Metrics

JavaScript

Metrics API

The Metrics API is exposed via the glue.metrics object. Metrics are organized in a hierarchical structure, using systems (systems can have sub-systems and metrics). In JavaScript, all metrics and metric sub-systems are created under a common App system, which sits under the root (/) system.

Creating Metrics Sub-Systems

To create a sub-system, you need to have a reference to a system and call its subSystem() function. Initially, there is one root system available via glue.metrics:

system.subSystem(
    name: String,
    description ?: String): System

Example:

const module = glue.metrics.subSystem("Module", "App Module");

Setting System State

The value of creating sub-systems is that it allows you to independently group attributes of your system and to flag its state. You can do that by setting the (sub-)system state, using the setState() function:

function setState(
  state: Number,
  description?: String): void

state is a number between 0 and 100:

Value Color Code Status
0 GREEN All good
50 AMBER Stuff works, some problems
100 RED Critical error

The description argument is a string explaining the state:

Examples:

// state RED, nothing works
module.setState(100, "Disconnected from backend");

// state GREEN, all good
module.setState(0, "(Re-)connected to backend");

// state AMBER, some problems
module.setState(50, "Backend operational but some " +
 "endpoints are not available");

Traversing Sub-Systems

The root system is glue.metrics, but it can also be accessed via the root property of any system:

const root = module.root;

You can:

  • find the parent of any sub-system via the parent property;
  • retrieve the child systems of a system using the subSystems property;
  • create almost arbitrary deep systems;

Example:

const module = glue.metrics.subSystem("A").subSystem("B"); 
// or
const module = glue.metrics.subSystem("A/B/C");

Working with Metrics

The JavaScript metrics library implements the following subset of Glue42 Metrics:

Type Description
string any text
number a floating point number
count an integer
timestamp a date/time value
address a host name, which gets resolved to an IP
timespan tracks and records elapsed time in milliseconds since epoch; can be used to track latency
object (or composite) allows you to create metrics with a non-scalar value

Object (or Composite) metrics are a set of key/value pairs, where the values are within the set of allowed metric values (boolean, int, long, double, string, date, and object).

Creating Metrics

Creating a metric under (sub-)system:

function someMetric(
    name: string | MetricDefinition,
    initialValue?: any): someMetric

Examples:

// the initial value is implicitly 0
const failures = module.countMetric("ajaxFailures");

// with a description and an initial value
const failures = module.countMetric({
    name: "ajaxFailuresWithDescription",
    description: "Number of times the AJAX requests have failed"
}, 42);


const expirationFactor = module.numberMetric("expirationFactor", 2.5);

const glueVersion = module.stringMetric("glueVersion", glue.version);

const hostName = module.addressMetric("hostName", "www.domain.com");

Latency in action:

// TRAINING NOTE: keep this 
const latency = module.timespanMetric("ajaxCallLatency");
latency.start();
latency.stop();

const err = new Error("Houston, we have a problem!");

const lastError = module.objectMetric("lastAjaxError", {
    // the first value defines the shape of the metric
    message: err.message,
    time: new Date(),
    stackTrace: err.stack
});

Updating Metrics

Metric values are updated using update() with the exception of TimespanMetric, which has start() and stop() methods, and NumberMetric and CountMetric which also have increment() and decrement() methods.

countMetric.update(10);     // set to 10
countMetric.increment();    // + 1
countMetric.increment(10);  // + 10

const url = "http://domain.com/api/v2/users";
lastUrl.update(url);
latency.start();

post(url)
    .then((result) => latency.stop())
    .catch((err) => {
        latency.stop();
        failures.increment();
        lastError.update({
            message: "Failed to create user: " + err.message,
            time: new Date,
            stackTrace: err.stack
        });
    });

Best Practices

Try to instrument your application as early as possible. Almost any web application performs a lot of AJAX requests, typically over REST calls to manipulate resources.

Avoid simply using your favorite AJAX library function everywhere. Instead, you should create a wrapper function which calls it, and is used throughout the entire application.

Maintain a metrics system for each endpoint, set its state to 100 (red) when the request fails, and back to 0 (green) when the request succeeds; then, under each system:

  • have a metric to count the number of requests, the number of failures and the number of successes;
  • have a timespan metric to record min/max/average latency;
  • have a composite metric to record the last failure reason (time of occurrence, message, and stacktrace, if possible)

Wrapping the function also gives you the ability to mock responses for testing, show and hide progress widgets in a consistent way, etc.

If you have instrumented your application properly, you should be able to debug any problem without having to look at log files, even if the problem is with the backend.

  • Configuration;
  • Status (e.g., connection status);
  • Activity (what is happening);
  • Errors;
  • Avoid leaking personal information (ID's are OK; names are not; URL's should be fine);

Reference

Reference