Courses/Pulse Analytics
Standalone Course~30 min10 challenges

Performance Analytics with Pulse: Tracing, Metrics & Monitoring

You cannot improve what you cannot measure. When your API is slow, which endpoint is the bottleneck? When memory usage spikes, what caused it? When errors happen in production, how do you find them? In this course, you will learn how Pulse — Grit's built-in observability SDK — gives you complete visibility into your running application.


What is Observability?

Observability is the ability to understand what your application is doing internally by examining its external outputs. In production, you cannot attach a debugger or add print statements. You need systems that continuously collect and present data about your application's behavior.

Observability: The measure of how well you can understand a system's internal state from its external outputs. A highly observable system lets you answer questions like "why is this endpoint slow?" or "what caused that error?" without deploying new code or restarting the application.

Observability rests on three pillars:

Logs: Timestamped text records of events: "User 42 logged in at 10:03:22," "Database query took 340ms," "Error: connection refused." Logs tell you WHAT happened. They are the most basic form of observability.
Metrics: Numerical measurements over time: request count, response time (p50, p95, p99), memory usage, CPU utilization, active database connections. Metrics tell you HOW MUCH is happening. They are cheap to collect and good for alerting.
Traces: End-to-end records of a single request as it flows through your system. A trace shows every step: the middleware, the handler, the database query, the external API call — with timing for each step. Traces tell you WHERE time is being spent.

Most observability solutions (Datadog, New Relic, Grafana Cloud) are external services that cost money and send your data to third-party servers. Pulse is different — it's built into your Grit application and runs locally. Your data stays with you.

1

Challenge: Three Pillars

In your own words, explain the difference between logging, metrics, and tracing. Give a concrete example of when you would use each one. For instance: when would a log be more useful than a metric? When would a trace be more useful than a log?

What is Pulse?

Pulse is Grit's built-in observability SDK. It's embedded directly in your API as middleware and provides a dashboard at /pulse/ui. No external services, no API keys, no monthly bills — just start your Grit API and open the dashboard.

What Pulse provides:

  • Request tracing — every API request timed and recorded with endpoint, method, status, and duration
  • Database monitoring — SQL query tracking, duration, and connection pool status
  • Runtime metrics — goroutine count, memory usage, garbage collection stats
  • Error tracking — automatic error capture with stack traces and frequency
  • Health checksGET /pulse/health for load balancers and uptime monitoring
  • Prometheus export/pulse/metrics for integration with Grafana
Pulse is designed for self-hosting. Your request data, error logs, and performance metrics never leave your infrastructure. This makes it suitable for applications with strict data residency requirements (GDPR, HIPAA) where sending telemetry to external services is not permitted.
2

Challenge: Open the Dashboard

Start your Grit API and open http://localhost:8080/pulse/ui in your browser. What sections does the dashboard have? Take note of the main navigation areas — you will explore each one in the following sections.

Request Tracing

Every HTTP request to your API is automatically traced by Pulse. For each request, Pulse records:

  • Endpoint — the URL path (e.g., /api/users)
  • Method — GET, POST, PUT, DELETE
  • Status code — 200, 201, 400, 401, 404, 500
  • Duration — how long the request took in milliseconds
  • Timestamp — when the request was made
  • Client IP — the origin of the request

The dashboard displays this data in a table, sorted by most recent. You can filter by endpoint, status code, or time range. The most useful view is sorting by duration — this instantly reveals your slowest endpoints.

What You See in Pulse
┌─────────┬───────────────────┬────────┬──────────┬─────────────────┐
│ Method  │ Endpoint          │ Status │ Duration │ Timestamp       │
├─────────┼───────────────────┼────────┼──────────┼─────────────────┤
│ POST    │ /api/auth/login   │ 200    │ 142ms    │ 10:03:22.481    │
│ GET     │ /api/users        │ 200    │ 23ms     │ 10:03:21.102    │
│ GET     │ /api/products     │ 200    │ 89ms     │ 10:03:20.847    │
│ POST    │ /api/products     │ 201    │ 31ms     │ 10:03:19.223    │
│ GET     │ /api/users/5      │ 404    │ 4ms      │ 10:03:18.991    │
│ DELETE  │ /api/products/3   │ 200    │ 12ms     │ 10:03:17.445    │
└─────────┴───────────────────┴────────┴──────────┴─────────────────┘
A response time under 100ms is good. Under 50ms is excellent. If you see endpoints consistently over 200ms, they likely have inefficient database queries — the Database Monitoring section (next) will help you find them.
3

Challenge: Find the Slowest Endpoint

Make at least 20 requests to different endpoints in your API — use the Scalar docs, curl, or the frontend. Then check the Pulse dashboard. Sort by duration. Which endpoint is the slowest? How much slower is it compared to the fastest? Can you guess why?

Database Monitoring

Most API performance issues are database issues. A single N+1 query can turn a 10ms endpoint into a 2-second endpoint. Pulse tracks every SQL query executed by GORM:

  • Query text — the actual SQL that was executed
  • Duration — how long the query took
  • Rows affected — how many rows were returned or modified
  • Frequency — how many times this query pattern was executed

Pulse also shows your connection pool status:

Connection Pool Metrics
Database Connection Pool
─────────────────────────
Idle connections:    5
Active connections:  2
Max connections:     25
Wait count:          0     ← requests waiting for a connection
Max idle time:       10m

If "Wait count" is greater than zero, your pool is too small — requests are waiting for an available database connection. If "Idle connections" is consistently at max, your pool is too large — those connections consume memory on the database server.

Look for query patterns, not individual queries. If you see SELECT * FROM products WHERE id = ? executed 100 times in a single request, you have an N+1 problem. The fix is usually a GORM Preload() to load relationships in a single query.
4

Challenge: Monitor Your Queries

Generate a resource with many records (e.g., seed 100 products). Make a request to list them with pagination. Check the Pulse database monitoring section. How many SQL queries were executed for that single API request? What is the slowest query?

Runtime Metrics

Go provides rich runtime information through its runtime package. Pulse collects and displays these metrics so you can understand how your application is using system resources.

Goroutine: Go's lightweight thread. Your API uses goroutines to handle concurrent requests — each incoming HTTP request is processed in its own goroutine. Unlike OS threads (which consume ~1MB of memory each), goroutines start at just 2KB. Thousands of goroutines are normal and expected.

Key runtime metrics Pulse tracks:

MetricWhat it MeansNormal Range
GoroutinesNumber of active goroutines10-100 (idle), 100-1000+ (under load)
Heap MemoryMemory allocated for Go objects20-200 MB depending on data size
System MemoryTotal memory obtained from the OSHigher than heap (includes stack, GC overhead)
GC PauseTime the garbage collector pauses your app<1ms is good, >10ms is concerning
GC RunsNumber of garbage collection cyclesFrequency depends on allocation rate
A steadily increasing goroutine count that never decreases is a goroutine leak. This usually means a goroutine is blocked waiting for something that will never happen (a channel that is never closed, a lock that is never released). Pulse helps you spot these leaks by showing goroutine count over time.
5

Challenge: Check Your Runtime

Open the Pulse dashboard and find the runtime metrics section. How many goroutines is your API running right now? What is the current memory usage? Make 50 rapid requests and check again — did the goroutine count increase? Did it come back down?

Error Tracking

When your API returns a 4xx or 5xx status code, Pulse captures the error automatically. For each error, it records:

  • Error message — what went wrong
  • Stack trace — where in the code the error occurred
  • Endpoint — which URL triggered the error
  • Frequency — how many times this error has occurred
  • Last occurrence — when it happened most recently

Errors are grouped by type and endpoint. If the same validation error occurs 500 times on the registration endpoint, it shows as one entry with a count of 500 — not 500 separate entries. This makes it easy to prioritize: fix the most frequent errors first.

Error Tracking View
┌───────────────────────────┬──────────┬───────┬───────────────────┐
│ Error                     │ Endpoint │ Count │ Last Seen         │
├───────────────────────────┼──────────┼───────┼───────────────────┤
│ record not found          │ GET /api │ 23    │ 2 minutes ago     │
│                           │ /users/  │       │                   │
│ validation: email required│ POST /api│ 8     │ 15 minutes ago    │
│                           │ /auth/   │       │                   │
│ duplicate key: email      │ POST /api│ 3     │ 1 hour ago        │
│                           │ /auth/   │       │                   │
└───────────────────────────┴──────────┴───────┴───────────────────┘
6

Challenge: Trigger and Track an Error

Cause an intentional error: request a resource with a non-existent ID (e.g.,GET /api/users/99999). Check the Pulse error tracking section. Does the error appear? What information does Pulse capture about it? Trigger it 5 more times and watch the count increase.

Health Checks

The health check endpoint at GET /pulse/health returns the current status of your API and its dependencies. This endpoint is used by:

  • Load balancers — to route traffic only to healthy instances
  • Kubernetes — as a liveness probe to restart unhealthy pods
  • Uptime monitors — services like UptimeRobot or Pingdom that alert you when your API goes down
  • Deployment scripts — to verify a new deployment is healthy before switching traffic
Health Check Response
GET /pulse/health

{
  "status": "healthy",
  "uptime": "4h 23m 12s",
  "checks": {
    "database": "connected",
    "redis": "connected",
    "storage": "connected"
  },
  "version": "1.0.0"
}

If any dependency is unhealthy (database disconnected, Redis unreachable), the status changes to "degraded" or "unhealthy" and the HTTP status code changes from 200 to 503 (Service Unavailable).

7

Challenge: Check Your Health

Call GET /pulse/health using your browser or curl. What does the response contain? What status code is returned? What happens if you stop your database (Docker container) and call the health endpoint again?

Prometheus Export

Prometheus: An open-source monitoring system that collects metrics from your applications by"scraping" HTTP endpoints at regular intervals (typically every 15 seconds). It stores the data as time series and provides a query language (PromQL) for analysis. Grafana is the standard tool for visualizing Prometheus data as dashboards and graphs.

Pulse exposes a /pulse/metrics endpoint in Prometheus format. This is a plain-text format that Prometheus understands:

GET /pulse/metrics (excerpt)
# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",endpoint="/api/users",status="200"} 142
http_requests_total{method="POST",endpoint="/api/auth/login",status="200"} 38
http_requests_total{method="GET",endpoint="/api/products",status="200"} 67

# HELP http_request_duration_seconds HTTP request duration in seconds
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{endpoint="/api/users",le="0.01"} 120
http_request_duration_seconds_bucket{endpoint="/api/users",le="0.05"} 138
http_request_duration_seconds_bucket{endpoint="/api/users",le="0.1"} 141

# HELP go_goroutines Number of goroutines
# TYPE go_goroutines gauge
go_goroutines 42

# HELP go_memstats_alloc_bytes Number of bytes allocated
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 2.4813e+07

To use this with Grafana, add a Prometheus data source pointing at http://your-api:8080/pulse/metrics and build dashboards with panels for request rate, error rate, response time percentiles, and resource usage.

You do not need Prometheus and Grafana to use Pulse. The built-in dashboard at /pulse/ui covers most needs. The Prometheus endpoint is for teams that already have a monitoring stack and want to integrate Grit's metrics into their existing dashboards.
8

Challenge: Read the Metrics

Visit http://localhost:8080/pulse/metrics in your browser. Can you read the Prometheus format? Find the total request count, the goroutine count, and the memory usage. What does the # TYPE line tell you about each metric (counter vs gauge vs histogram)?

Configuration

Pulse is configured through environment variables in your .env file:

VariableDefaultPurpose
PULSE_ENABLEDtrueEnable or disable Pulse entirely
PULSE_USERNAMEadminUsername for the Pulse dashboard
PULSE_PASSWORDpulsePassword for the Pulse dashboard
.env
# Pulse Configuration
PULSE_ENABLED=true
PULSE_USERNAME=admin
PULSE_PASSWORD=your-secure-password-here
Always change the default Pulse credentials in production. The dashboard exposes sensitive information about your API's internals — response times, error messages, database queries, memory usage. This data is valuable to attackers for reconnaissance.
9

Challenge: Secure Your Dashboard

Change the Pulse password in your .env file. Restart the API. Try accessing /pulse/ui — are you prompted for credentials? Verify that the old password no longer works and the new one does.

Summary

Let's review what Pulse provides:

FeatureEndpointPurpose
Dashboard/pulse/uiVisual overview of all metrics
Request TracingDashboardFind slow endpoints
Database MonitorDashboardFind slow queries, pool status
Runtime MetricsDashboardGoroutines, memory, GC
Error TrackingDashboardGrouped errors with frequency
Health Check/pulse/healthLoad balancer / uptime monitoring
Prometheus/pulse/metricsIntegration with Grafana
10

Challenge: Final Challenge: Performance Baseline

Establish a performance baseline for your API. Record the response time of these 5 key operations (run each one 5 times and take the average):

  1. POST /api/auth/login — authentication
  2. GET /api/users — list with pagination
  3. POST /api/products — resource creation
  4. GET /pulse/health — health check
  5. GET /api/products?page=1&page_size=50 — large page size

Write down the average response time for each. This is your baseline. As you add features, run these same tests to detect performance regressions. If login goes from 140ms to 800ms after a change, you know something is wrong.