Last9

What are Application Metrics?

Application metrics are key performance signals, like latency, error rate, and throughput, that help you understand how your app behaves in production.

Aug 4th, ‘25
What are Application Metrics?
See How Last9 Works

Unified observability for all your telemetry. Open standards. Simple pricing.

Talk to an Expert

Application metrics are structured, quantifiable signals that reflect how your software behaves in production. They capture key aspects of performance, response times, error rates, throughput, and resource usage, giving you a real-time view into the health of your system.

Tracking the right metrics helps detect regressions early, surface latent issues before they impact users, and guide optimization decisions based on hard data, not guesswork.

In this blog, we’ll break down the core metrics every application should expose, practical ways to monitor them, and how to use that data to improve reliability, performance, and user experience.

Introduction to Application Metrics

Application metrics are signals that describe how your software behaves at runtime. Unlike infrastructure metrics, which monitor host-level details like CPU or disk usage, application metrics track what’s happening inside the app itself.

For example:

  • API response times
  • Error rates by endpoint
  • Memory usage during peak traffic
  • Database query latency

These metrics give you a clear view of what users are experiencing. Your infrastructure might look fine, but if requests are timing out or key workflows are failing, the app is still broken from the user’s perspective. Application metrics make that gap visible. They help you catch slowdowns and regressions before they impact users, and give you the data to figure out where and why performance is slipping.

💡
To track both request-level data and what’s happening inside your Java app, check out our JVM metrics guide.

Types of Application Metrics

Application metrics usually fall into three categories. Each one answers a different question about your system.

1. System-Level Metrics
These measure how the application uses resources internally.
Useful for tuning performance and troubleshooting under load.

Examples:

  • Memory allocation and garbage collection times
  • CPU time by worker thread
  • Queue depths in background job processors
  • Thread or connection pool usage

2. Business-Level Metrics
These track how users interact with your application.
Useful for connecting system behavior to product outcomes.

Examples:

  • Number of logins or signups
  • Transactions completed per minute
  • Failure rates during checkout
  • Usage counts for specific features

These are typically custom metrics defined in code, often exposed via counters or gauges.

3. Application-Specific Metrics
These capture logic unique to your system or domain.
Useful for monitoring critical code paths or debugging domain behavior.

Examples:

  • Cache hit/miss ratios
  • Latency of a recommendation engine
  • Retry attempts to external APIs
  • Failure rates for domain-specific rules or workflows

Each type of metric offers a different perspective:

  • System metrics show how your app is running
  • Business metrics show what your users are doing
  • App-specific metrics show why certain behaviors or failures occur

Together, they give you a complete view, from internal resource usage to user-facing outcomes.

💡
If you’re monitoring application performance in containerized environments, our Docker container performance metrics guide shows how to combine container-level insights with request-level metrics.

Performance Metrics in Software Applications

When we talk about performance monitoring in applications, it usually boils down to three key metrics: response time, throughput, and error rate.

Response Time
This is how long your application takes to handle a request, from the time it’s received to when the response is sent. It includes everything: app logic, database calls, network hops, and any queueing delays.

Users can feel even small delays. Response times under 100ms feel fast. Cross the 1-second mark, and users start noticing. Push past that, and they’ll drop off. If there’s one metric to stay on top of, it’s this one.

Throughput
Throughput is the rate at which your application handles requests.

  • Measured in requests per second (RPS) or transactions per minute (TPM).
  • Gives you a sense of capacity: how much load your system can take before performance drops.

Watching throughput alongside response time helps you detect when the system is under pressure, like when spikes in traffic start affecting latency.

Error Rate
This is the percentage of failed requests. It includes:

  • HTTP 5xx and 4xx responses
  • Exceptions in your application
  • Business-level failures (e.g., payment declined, auth errors)

A sudden spike in error rate usually signals that something’s broken, even if the system is technically “up.”

Add Context with CPU and Memory Usage
While response time, throughput, and error rate tell you what users are seeing, CPU and memory usage help explain why.

  • High CPU usage could point to inefficient code paths or tight loops.
  • Steadily growing memory might mean a leak or misconfigured cache.

These resource-level metrics are especially useful when performance degrades without a clear failure.

Example: Collecting Basic Performance Metrics (Node.js)

const performanceMetrics = {
  responseTime: Date.now() - requestStartTime,
  memoryUsage: process.memoryUsage().heapUsed,
  activeConnections: server.connections,
  errorCount: errorCounter.get()
};

metricsCollector.record(performanceMetrics);

Zooming In on Response Time

Response time is the one metric your users feel. Every time someone clicks a button, loads a page, or submits a form, they’re waiting for your system to respond. That wait defines how fast or slow your app feels.

But response time is a combination of several components:

  • How long your app takes to process the request
  • Time spent querying the database
  • Network latency between services
  • Delays caused by queueing (e.g., thread pool or message broker)

Understanding which part of the request path is slow is the first step toward making it faster.

Why It Gets Tricky in Distributed Systems
In a monolith, response time is mostly about code and DB latency. But in a distributed system, a single user request might fan out to several internal services. Each hop adds latency, and sometimes failure risk.

This is where end-to-end tracing becomes valuable. You want to monitor both:

  • Overall request latency
  • Latency of each internal service involved

That’s how you identify issues like cascading delays or a single slow dependency dragging everything down.

How to Bring Down Latency
Once you know where the time is going, you can start fixing it. Some common ways to reduce latency:

  • Use caching for frequently accessed data
  • Optimize slow DB queries (indexes, query plans, etc.)
  • Batch or debounce expensive operations
  • Use connection pooling for DBs or external services
  • Cut down on payload size and avoid heavy serialization
  • Avoid blocking calls on high-traffic code paths
  • Profile the critical path, the sequence of steps that must finish before a response is returned

Example: Measuring Response Time by Component (Python)

import time
from contextlib import contextmanager

@contextmanager
def measure_time(operation_name):
    start = time.time()
    try:
        yield
    finally:
        duration = time.time() - start
        metrics.record(f"{operation_name}_duration", duration)

def process_request(user_id):
    with measure_time("database_query"):
        user_data = database.get_user(user_id)
    
    with measure_time("business_logic"):
        result = process_user_data(user_data)
    
    with measure_time("cache_write"):
        cache.set(f"user_{user_id}", result)
    
    return result

This instrumentation makes it easier to figure out where time is being spent, so you’re not assuming which part of the stack needs tuning.

💡
Tracking custom metrics or managing async workloads? Our Amazon SQS metrics guide shows how to monitor queue behavior and spot lag early.

Monitor Garbage Collection and Uptime

In managed runtimes like Java, C#, and Python, garbage collection (GC) automates memory management—but it also introduces latency. GC pauses can slow down response times, especially under load or in latency-sensitive systems.

In Java, different GC algorithms behave differently:

  • G1 GC aims for predictable pauses.
  • ZGC and Shenandoah are built for ultra-low latency, even with large heaps.

To understand GC behavior, track:

  • Collection frequency
  • Pause durations
  • Heap and memory pool usage

In Python, the runtime uses reference counting with cyclic garbage detection. Pause times are typically lower than in Java, but memory leaks can still occur, especially with circular references or long-lived objects.

Useful metrics include:

  • Object creation rates
  • Unreachable object counts
  • Collection cycle frequency

A Few Ways to Tune GC for Better Performance

  • Right-size your heap to avoid excessive GC cycles
  • Adjust collection thresholds based on memory usage patterns
  • Pool frequently created short-lived objects
  • Limit the scope of long-lived object references (e.g., in caches)

Example: Export GC Metrics in Java

public class GCMetricsCollector {
    private final List<GarbageCollectorMXBean> gcBeans;

    public void collectGCMetrics() {
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            long collections = gcBean.getCollectionCount();
            long time = gcBean.getCollectionTime();

            metrics.gauge("gc.collections", collections, 
                          "collector", gcBean.getName());
            metrics.gauge("gc.time", time, 
                          "collector", gcBean.getName());
        }

        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();

        metrics.gauge("memory.heap.used", heapUsage.getUsed());
        metrics.gauge("memory.heap.max", heapUsage.getMax());
    }
}

Maintain Uptime and Application Availability

Uptime reflects whether your application is available and functional. It’s usually measured as a percentage; 99.9% uptime means about 8.77 hours of downtime in a year. Uptime numbers often appear in SLAs and directly influence user trust.

But uptime isn’t just about whether your server responds to pings. It’s about whether your app can request and complete real workflows.

What Good Uptime Monitoring Looks Like

  • Tests end-to-end user flows, not just endpoints
  • Validates business-critical operations (e.g., logins, purchases)
  • Detects partial failures (e.g., one API is slow, but others work)

Synthetic monitoring simulates real usage to verify that everything works, not just that it’s online.

Techniques to Improve Application Availability

  • Use liveness probes to restart hung services
  • Use readiness probes to control traffic flow to unhealthy instances
  • Add circuit breakers to prevent failures from cascading
  • Set up failover and auto-scaling policies
  • Adopt blue-green or canary deployments to minimize release-related downtime

Monitor More Than Just Uptime
Even when your app is “up,” internal dependencies can silently fail:

  • Database connection pool exhaustion
  • Message queue backlogs
  • Third-party API outages

By monitoring the full stack—app health, infra dependencies, external services—you get a more accurate picture of availability.

Example: Kubernetes Health Checks

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: app
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
  • Liveness probes detect deadlocks or hangs and trigger restarts.
  • Readiness probes prevent traffic from hitting an instance before it’s fully ready.

Both are critical for reducing downtime and improving resilience in containerized environments.

💡
Combining application metrics with container- or JVM-level observability offers a fuller performance picture, our metrics monitoring blog explains how to bring everything together.

Application Performance Monitoring with OpenTelemetry and Prometheus

Good observability starts with lightweight telemetry. You want to track how your app behaves, without slowing it down. That’s where OpenTelemetry comes in. It gives you a consistent, vendor-neutral way to collect metrics, traces, and logs across your services.

Prometheus remains a solid choice for storing and querying metrics. Its pull-based model is great for long-running applications, and PromQL lets you ask sharp questions about system behavior. Pair it with Grafana, and you’ve got a powerful setup for visualization and alerting.

But things get tricky when metric volume grows, especially with high-cardinality labels. That’s where Last9 steps in. Built to work natively with OpenTelemetry and fully compatible with Prometheus, Last9 is a managed telemetry data platform designed to handle scale.

Here’s what basic OpenTelemetry instrumentation with Prometheus will look like:

from opentelemetry import metrics
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from opentelemetry.sdk.metrics import MeterProvider
import time

# Set up Prometheus exporter
metric_reader = PrometheusMetricReader()
meter_provider = MeterProvider(metric_readers=[metric_reader])
metrics.set_meter_provider(meter_provider)
meter = metrics.get_meter("my-application")

# Define metrics
request_counter = meter.create_counter(
    "requests_total", description="Total number of requests"
)
response_time_histogram = meter.create_histogram(
    "response_time_seconds", description="Response time in seconds"
)

# Track incoming requests
def handle_request(request):
    start = time.time()
    try:
        response = process_request(request)
        request_counter.add(1, {"status": "success", "endpoint": request.path})
        return response
    except Exception:
        request_counter.add(1, {"status": "error", "endpoint": request.path})
        raise
    finally:
        duration = time.time() - start
        response_time_histogram.record(duration, {"endpoint": request.path})

This setup gives you request counts, response times, and endpoint-level visibility, all exported in a Prometheus-compatible format. Plug it into Last9, and you get queryable, high-resolution telemetry without worrying about scale, cost, or downtime.

💡
You can start exporting metrics to Last9 and use PromQL for queries with a simple setup!

Find and Fix Performance Bottlenecks

Performance slowdowns often have multiple contributing factors. A single slow database query, a saturated connection pool, or a delayed external API can ripple through your system. Understanding these connections is key to solving them effectively.

Start with the critical path
Trace the full journey of a request, from the user action to the final response. Instrument key steps so you can identify where latency adds up. Common hotspots include:

  • Database queries – missing indexes or inefficient joins
  • Third-party service calls – slow responses or retries
  • Compute-heavy logic – loops, sorting, or transformations

Use profilers that work in production
Lightweight profilers can continuously capture method-level performance without introducing overhead. Look for:

  • CPU and memory usage by function
  • Long-running methods
  • Flame graphs to visualize call stacks

Backend tuning strategies

  • Refactor expensive queries and ensure critical indexes exist
  • Use caching to avoid redundant computation or I/O
  • Set up connection pooling to manage load efficiently
  • Scale read-heavy workloads using replicas

Measure what users experience
Not all performance issues come from the backend. Frontend metrics often highlight user-visible delays. Track:

  • Time to First Byte (TTFB) – how quickly the first response arrives
  • First Contentful Paint (FCP) – when initial UI appears
  • Largest Contentful Paint (LCP) – when the main content is rendered

Example: Detecting Bottlenecks in JavaScript

class PerformanceTracker {
  constructor() {
    this.operations = new Map();
  }

  startOperation(name) {
    this.operations.set(name, Date.now());
  }

  endOperation(name) {
    const start = this.operations.get(name);
    if (!start) return;

    const duration = Date.now() - start;

    if (duration > this.getThreshold(name)) {
      this.alertSlowOperation(name, duration);
    }

    metrics.histogram('operation_duration', duration, { operation: name });
    this.operations.delete(name);
  }

  getThreshold(operation) {
    return {
      'database_query': 100,
      'api_call': 500,
      'cache_lookup': 10
    }[operation] || 1000;
  }
}

Conclusion

Application metrics give you the feedback loop you need to understand how your system behaves under load. Start with the core signals—latency, errors, throughput, and build toward metrics that reflect your business logic and user experience.

At Last9, we built a system from the ground up to handle high-cardinality telemetry. Instead of indexing every label combination like traditional TSDBs, Last9 aggregates metrics at write time using a streaming engine. Data is compressed and bucketed on ingest, so queries stay fast, even with millions of unique series.

This means no pre-filtering, no dropped dimensions, and no guesswork about what to instrument. Just query the metrics you care about, by any tag or time window, and get answers in seconds.

Probo Cuts Monitoring Costs by 90% with Last9
Probo Cuts Monitoring Costs by 90% with Last9

Try it for free or talk to us to see how it fits your setup!

FAQs

What's the difference between application metrics and infrastructure metrics?

Application metrics measure the behavior of your software code—response times, error rates, and business logic performance. Infrastructure metrics track the underlying systems—server CPU, memory, disk I/O. Both are important, but application metrics directly reflect what users experience.

How often should I collect application metrics?

For real-time monitoring, collect metrics every 10-15 seconds. For trending and capacity planning, 1-minute intervals are sufficient. High-frequency collection provides better alerting responsiveness but increases storage costs and processing overhead.

Which application metrics are most important for user experience?

Response time, error rate, and availability are the core metrics that users feel directly. Add throughput to understand capacity limits and business-specific metrics like transaction success rates or feature usage patterns.

How do I monitor application performance in microservices?

Track metrics at service boundaries (request rate, latency, errors for each service), monitor inter-service dependencies, and use distributed tracing to understand request flows across services. Service mesh tools can provide automatic metrics collection.

What should I do when application metrics show performance degradation?

First, check if the degradation correlates with recent deployments or configuration changes. Look at related metrics (CPU, memory, database performance) to identify bottlenecks. Use application profiling to pinpoint specific code paths causing issues.

How can I reduce the cost of collecting application metrics?

Use sampling for high-volume, low-priority metrics. Avoid high-cardinality labels like user IDs in metrics; use logs for that level of detail. Set up retention policies to store detailed metrics short-term and aggregated metrics long-term.

What's the best way to alert on application metrics? Alert on symptoms users experience, not every possible failure. Use error rate thresholds (like >1% over 5 minutes) rather than single error occurrences. Combine multiple signals, alert when both the error rate increases AND the response time degrades.

Authors
Anjali Udasi

Anjali Udasi

Helping to make the tech a little less intimidating. I

Contents

Do More with Less

Unlock unified observability and faster triaging for your team.