ARMS provides common Application Monitoring metrics. You can import the OpenTelemetry Java SDK to customize metrics. This topic describes how to customize metrics and query them in Grafana.
Prerequisites
You have connected your application to ARMS Application Monitoring. For more information, see Application access.
The ARMS agent version is 4.5.0 or later.
Import dependencies
Import the OpenTelemetry Java SDK by adding the following Maven dependency. For more information, see the official OpenTelemetry document.
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-trace</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.23.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Steps to customize metrics
OpenTelemetry currently supports the following four types of metrics. ARMS supports all types except for Histogram.
CounterUsage: Records monotonically increasing cumulative values.Example: Total number of HTTP requests and number of faults.
UpDownCounterUsage: Records non-monotonic values that can increase or decrease, such as the number of active connections.Example: Number of concurrent requests and number of tasks in a queue.
Histogram (not supported)Usage: Records the distribution of values, such as latency and response size, and supports quantile calculation.Example: Request latency distribution (P90 and P95) and file upload size.
GaugeUsage: Captures an instantaneous value at a specific time, such as CPU utilization or memory usage.Example: System temperature and real-time memory usage.
Step 1: Add custom metrics to the code
The following code shows a simple flash sale example that defines two metrics:
product_seckill_count: The number of flash sale attempts.
product_current_stock: The current inventory.
When you retrieve the meter, which is a factory class for defining metrics, you pass in a parameter named product_seckill. This parameter acts as a group name. All metrics defined by this meter belong to this group. This group name is used in subsequent configurations.
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongGauge;
import javax.annotation.PreDestroy;
import java.util.concurrent.atomic.AtomicInteger;
class ProductService {
// Static inventory counter
public final AtomicInteger stock = new AtomicInteger(0);
private LongCounter seckillCounter;
private ObservableLongGauge observableLongGauge;
private final AttributeKey<String> seckillResult = AttributeKey.stringKey("seckill_result");
public ProductService() {
OpenTelemetry agentOpenTelemetry = GlobalOpenTelemetry.get();
// Define the metric factory class. Note that product_seckill is important.
Meter meter = agentOpenTelemetry.getMeter("product_seckill");
// Define a counter metric to record the number of flash sale attempts.
seckillCounter = meter.counterBuilder("product_seckill_count")
.setUnit("1")
.setDescription("seckill product count")
.build();
// Define a gauge metric to represent the current inventory of the product.
observableLongGauge = meter.gaugeBuilder("product_current_stock").ofLongs().buildWithCallback((measurement -> {
// Record the current product quantity.
measurement.record(stock.get());
}));
}
@PreDestroy
public void clear() {
observableLongGauge.close();
}
public void setKillProductCount(int count) {
stock.set(count);
}
public String seckillProduct() {
int currentStock = stock.get();
if (currentStock <= 0) {
seckillCounter.add(1, Attributes.of(seckillResult, "failed"));
return "Flash sale failed. The product is sold out.";
}
// Try to deduct the inventory.
if (stock.decrementAndGet() >= 0) {
seckillCounter.add(1, Attributes.of(seckillResult, "success"));
return "Flash sale successful. Remaining inventory: " + stock.get();
} else {
stock.incrementAndGet(); // Rollback
seckillCounter.add(1, Attributes.of(seckillResult, "success"));
return "Flash sale failed. The product is sold out.";
}
}
}
Step 2: Configure metric collection in the console
In the console, modify the Custom Metric Collection Configuration and add the parameter that you used to create the meter in the previous step.

Step 3: View metrics and configure alerts
In the top menu bar of the page in the ARMS console, select the region where your application is deployed.
The instance with the type Prometheus Instance for Application Monitoring is the default Application Monitoring instance.

Click Shared Edition to open the Grafana page. Click Explore and select the Prometheus instance from the previous step as the data source.

You can use Prometheus Query Language (PromQL) to query the metrics that you defined in the code, as shown in the following figure. You can also Create a custom Grafana dashboard in Grafana.

At this point, the metrics that you defined using the OpenTelemetry SDK are reported to and stored in the Prometheus instance in ARMS. You can then create alert rules for these metrics.
Notes
ARMS reports metrics at a 15-second interval.
For Counter metrics, ARMS reports the increment within each reporting epoch, not the cumulative value. This means that ARMS reports the increase in the metric's value every 15 seconds.