Metrics
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
The Genesis Metrics module enables you to capture metrics for specific components of your application. You achieve this by inserting programmatic calls into appropriate places in your code.
Any metrics system will have an effect on the performance of the application it is monitoring. The extra code (log statements, metrics, etc) will have an impact in some way.
Genesis uses the well-known metrics library, which is commonly used in Java apps. In all known Genesis applications, the impact is negligible; the benefits far outweigh the very tiny impact on performance.
To make use of the metric calls, you must set MetricsEnabled
to true
in the system definition file. In addition, you should define the MetricsReportType
to include a comma-separated list of MetricsReportType
outputs, which should include at least one of the following:
- Console - sends metrics straight to the console
- SLF4J - will append metrics to an SLF4J Logger
- GRAPHITE - will send metrics to a Graphite service, which needs to be up and running
- This requires some additional settings for
MetricsGraphiteURL
andMetricsGraphitePort
which identify the Graphite server.
- This requires some additional settings for
Set-up (example using SLF4J and GRAPHITE)
In this example, we use a SLF4J log and Graphite server to capture metrics. Detailed set-up of a Graphite server is beyond the scope of this document, but it can be run in a docker container as described here
item(name = "MetricsEnabled", value = "true")
item(name = "MetricsReportType", value = "GRAPHITE,SLF4J")
item(name = "MetricsGraphiteURL", value = "localhost")
item(name = "MetricsGraphitePort", value = "2003")
item(name = "MetricsReportIntervalSecs", value = "60") // Optional, defaults to 10 seconds if not specified
item(name = "Slf4jReporterLoggingLevel", value = "DEBUG") // Optional, defaults to DEBUG, options include {DEBUG, INFO, TRACE, WARN, ERROR}
Metrics API
To use the API, include the following in your project dependencies:
<Tabs defaultValue="maven" values={[{ label: 'Gradle', value: 'gradle', }, { label: 'Maven', value: 'maven', }]}>
implementation("global.genesis:genesis-metrics")
<dependency>
<groupId>global.genesis</groupId>
<artifactId>genesis-metrics</artifactId>
</dependency>
The object MetricService
is available via the dependency injection mechanism. Simply mark a constructor with the @Inject
annotation and the parameters will be resolved automatically.
Once the MetricService
object is in scope, you can invoke methods on it to retrieve the appropriate metric objects.
The MetricService
distinguishes between two different kind of metrics; process and resource.
Process metrics are values that will exist once and only once per process, current memory utilisation, GC stats etc.
Resource metrics represent data about a named resource that may be one or more resources hosted within a process. Dataservers, event handlers, reqreps and consolidators are all examples of named resources.
For a consistent approach and to make things easier for operations and support staff, the Genesis framework enforces a standard convention for the naming of metrics.
For process level metrics:
genesis.$groupName.$processName.$hostname.process.$metricClassifier.$metricName
For resource level metrics:
genesis.$groupName.$processName.$hostname.$resourceClassifier.$resourceName.$metricClassifier.$metricName
Where:
$groupName
is the value in the groupId field of the process definition (or unknown if missing)
$processName
is the name defined in the processes definition
$hostname
is the machine hostname
$resourceClassifier
is the type of resource being monitored in the plural form, e.g. dataservers
$resourceName
is the name given to the resource in its XML or GPAL definition
$metricClassifier
is a qualifying name for the metric value, used to organise the resulting dir structure
$metricName
is the thing actually being measured, such as 'latency', 'rate', 'count'
The path separator is a period '.'
Any of the above parameters can contain any number of periods to further sub-divide the classifiers as required.
Path sanitisation is performed in order to ensure path consistency. Any character in the following set:
*@/\’”;:|[]{}()&^%$,
will be replaced with an underscore.
To create metrics, use the [meter], [timer], [histogram] and [counter] functions.
You can also register custom gauge implementations using the [registerCustomGauge] function.
The metric functions [meter], [timer], [histogram] and [counter] are also lookup functions into the metric registry. As such, path sanitisation only occurs if the non-sanitised path does not already have a mapping. This is to avoid string scans and regex evaluation on every lookup to retrieve a metric object. In order to maximise performance (for example when counting messages in a high volume stream) do not use any upper-case or forbidden characters in your classifiers or names, or store the metric object in a local variable and do not perform a lookup each time it needs to be used.
Counters
<Tabs defaultValue="kotlin" values={[{ label: 'Kotlin', value: 'kotlin', }, { label: 'Java', value: 'java', }]}>
class UserAuthentication @Inject constructor(
val metricService: MetricService
) {
fun login(user: User) {
val userLoginCounter = metricService.counter("users", user.userName, "active-sessions", "count")
userLoginCounter.increment()
// functional code would go here
}
fun logout(user: User) {
val userLoginCounter = metricService.counter("users", user.userName, "active-sessions", "count")
userLoginCounter.decrement()
// functional code would go here
}
}
class UserAuthentication {
private final MetricService metricService;
@Inject
public UserAuthentication(MetricService metricService) {
this.metricService= metricService;
}
void login(User user) {
var userLoginCounter = metricService.counter("users", user.userName, "active-sessions", "count")
userLoginCounter.increment();
// functional code would go here
}
void logout(User user) {
var userLoginCounter = metricService.counter("users", user.userName, "active-sessions", "count")
userLoginCounter.decrement();
// functional code would go here
}
}
Meters
<Tabs defaultValue="kotlin" values={[{ label: 'Kotlin', value: 'kotlin', }, { label: 'Java', value: 'java', }]}>
class UserAuthentication @Inject constructor(
val metricService: MetricService
) {
fun login(user: User) {
val throughput = metricService.meter("groups", user.groupName, "login", "rate")
throughput.mark()
// functional code would go here
}
}
class UserAuthentication {
private final MetricService metricService;
@Inject
public UserAuthentication(MetricService metricService) {
this.metricService= metricService;
}
void login(User user) {
var throughput = = metricService.meter("users", user.userName, "login", "rate")
throughput.mark();
// functional code would go here
}
}
Latency
<Tabs defaultValue="kotlin" values={[{ label: 'Kotlin', value: 'kotlin', }, { label: 'Java', value: 'java', }]}>
class UserAuthentication @Inject constructor(
val metricService: MetricService
) {
fun login(user: User) {
val userLoginTime = metricService.latency("users", user.userName, "login", "latency").time()
// functional code would go here
userLoginTime.stop()
}
}
class UserAuthentication {
private final MetricService metricService;
@Inject
public UserAuthentication(MetricService metricService) {
this.metricService= metricService;
}
void login(User user) {
LatencyContext userLoginTime = metricService.latency("users", user.userName, "login", "latency").time()
// functional code would go here
userLoginTime.stop();
}
}
Histograms
<Tabs defaultValue="kotlin" values={[{ label: 'Kotlin', value: 'kotlin', }, { label: 'Java', value: 'java', }]}>
class Queue @Inject constructor(val name: String, val metricService: MetricService) {
fun queueRequest(request: MetricUtilsTest.Request) {
val histogram = metricService.histogram("queues", name, "size", "histogram")
histogram.update(request.size.toLong())
// functional code would go here
}
}
class Queue {
private String name;
private final MetricService metricService;
@Inject
public Queue(String name, MetricService metricService)
void queueRequest(Request request) {
var histogram = metricService.histogram("queues", name, "size", "histogram");
histogram.update(request.size);
// functional code would go here
}
}