Of course, you can also use the Prometheus client library to export your own metrics. These do not need to be technical metrics that reflect some aspect of the Go runtime (such as CPU usage and memory allocation), but it could also be business metrics. One possible example could be the amount of booked tickets with different labels per event.
For example, within the todo.com/myevents/bookingservice/rest package, you could add a new file—let's call it metrics.go—that declares and registers a new Prometheus metrics:
package rest
import "github.com/prometheus/client_golang/prometheus"
var bookingCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "bookings_count",
Namespace: "myevents",
Help: "Amount of booked tickets",
},
[]string{"eventID", "eventName"},
)
func init() {
prometheus.MustRegister(bookingCount)
}
The Prometheus client library tracks all created metric objects in a package, a global registry that is automatically initialized. By calling the prometheus.MustRegister function, you can add new metrics to this registry. All registered metrics will automatically be exposed when a Prometheus server scrapes the /metrics endpoint.
The NewCounterVec function used creates a collection of metrics that are all named myevents_bookings_count but are differentiated by two labels, eventID and eventName (in reality, these are functionally dependent and you wouldn't really need both; but having the event name as a label comes in handy when visualizing this metric in Grafana). When scraped, these metrics might look like this:
myevents_bookings_count{eventID="507...",eventName="Foo"} 251
myevents_bookings_count{eventID="508...",eventName="Bar} 51
The Prometheus client library knows different types of metrics. The Counter that we used in the preceding code is one of the simpler ones. In one of the previous sections, you saw how a complex histogram was represented as a number of different metrics. This is also possible with the Prometheus client library. Just to demonstrate, let's add another metric—this time, a histogram:
var seatsPerBooking = prometheus.NewHistogram(
prometheus.HistogramOpts{
Name: "seats_per_booking",
Namespace: "myevents",
Help: "Amount of seats per booking",
Buckets: []float64{1,2,3,4}
}
)
func init() {
prometheus.MustRegister(bookingCount)
prometheus.MustRegister(seatsPerBooking)
}
When being scraped, this histogram will be exported as seven individual metrics: you will get five histogram buckets (Number of bookings with one seat or less up to Four seats or less and Infinitely many seats or less), and one metric for the sum of all seats and sum of all observations, respectively:
myevents_seats_per_booking_bucket{le="1"} 1
myevents_seats_per_booking_bucket{le="2"} 8
myevents_seats_per_booking_bucket{le="3"} 18
myevents_seats_per_booking_bucket{le="4"} 20
myevents_seats_per_booking_bucket{le="+Inf"} 22
myevents_seats_per_booking_sum 72
myevents_seats_per_booking_count 22
Of course, we will need to tell the Prometheus library the values that should be exported for these metrics when scraped by the Prometheus server. Since both metrics (amount of bookings and amount of seats per booking) can only change when a new booking is made, we can add this code to the REST handler function that handles POST requests on the /events/{id}/bookings route.
In the booking_create.go file, add the following code somewhere after the original request has been processed (for example, after the EventBooked event is emitted on the event emitter):
h.eventEmitter.emit(&msg)
bookingCount.
WithLabelValues(eventID, event.Name).
Add(float64(request.Seats))
seatsPerBooking.
Observe(float64(bookingRequest.Seats))
h.database.AddBookingForUser(
// ...
The first statement adds the amount of booked seats (request.Seats) to the counter metric. Since you defined one label named event in the CounterVec declaration, you will need to call the WithLabelValues method with the respective label values (if the metric declaration consisted of two labels, you would need to pass two parameters into WithLabelValues).
The second statement adds a new observation to the histogram. It will automatically find the correct bucket and increment it by one (for example if three seats are added with the same booking, the myevents_seats_per_booking_bucket{le="3"} metric will be increased by one).
Now, start your application and make sure that Prometheus is scraping it at regular intervals. Take the time and add a few example records to your application. Also, add a few event bookings at the booking service; ensure that you do not create them all at once. After that, you can use the myevents_bookings_count metric to create a new graph in your Grafana dashboard:
>
Gafana graph
By default, Prometheus will create one time series per scraped instance. This means that when you have multiple instances of the booking service, you will get multiple time series, each with a different job label:
myevents_bookings_count{eventName="Foo",job="bookingservice-0"} 1
myevents_bookings_count{eventName="Foo",job="bookingservice-1"} 3
myevents_bookings_count{eventName="Bar",job="bookingservice-0"} 2
myevents_bookings_count{eventName="Bar",job="bookingservice-1"} 1
When displaying a business metric (for example, the number of tickets sold), you may not actually care at which instance each particular booking was placed and prefer an aggregated time series over all instances, instead. For this, you can use the PromQL function sum() when building your dashboard:
sum(myevents_bookings_count) by (eventName)