Files
zeekay d32e075809 refactor: update to vm v1.0.18, migrate log and metrics packages
- Update vm dependency to v1.0.18
- Remove internal log package in favor of luxfi/log
- Migrate prometheus to gatherer package
2026-01-11 21:38:34 -08:00

136 lines
3.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package metrics
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
log "github.com/luxfi/log"
"github.com/luxfi/metric"
)
type Metrics struct {
reg metric.Registry
// Summary of the quantiles of Individual Issuance Tx Times
IssuanceTxTimes metric.Summary
// Summary of the quantiles of Individual Confirmation Tx Times
ConfirmationTxTimes metric.Summary
// Summary of the quantiles of Individual Issuance To Confirmation Tx Times
IssuanceToConfirmationTxTimes metric.Summary
}
func NewDefaultMetrics() *Metrics {
registry := metric.NewRegistry()
return NewMetrics(registry)
}
// NewMetrics creates and returns a Metrics and registers it with a Collector
func NewMetrics(reg metric.Registry) *Metrics {
m := &Metrics{
reg: reg,
IssuanceTxTimes: metric.NewSummary(metric.SummaryOpts{
Name: "tx_issuance_time",
Help: "Individual Tx Issuance Times for a Load Test",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
}),
ConfirmationTxTimes: metric.NewSummary(metric.SummaryOpts{
Name: "tx_confirmation_time",
Help: "Individual Tx Confirmation Times for a Load Test",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
}),
IssuanceToConfirmationTxTimes: metric.NewSummary(metric.SummaryOpts{
Name: "tx_issuance_to_confirmation_time",
Help: "Individual Tx Issuance To Confirmation Times for a Load Test",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
}),
}
return m
}
type MetricsServer struct {
metricsPort string
metricsEndpoint string
cancel context.CancelFunc
stopCh chan struct{}
}
func (m *Metrics) Serve(ctx context.Context, metricsPort string, metricsEndpoint string) *MetricsServer {
ctx, cancel := context.WithCancel(ctx)
// Create a metrics server to expose individual tx metrics
server := &http.Server{
Addr: fmt.Sprintf(":%s", metricsPort),
}
// Start up go routine to listen for SIGINT notifications to gracefully shut down server
go func() {
// Blocks until signal is received
<-ctx.Done()
if err := server.Shutdown(ctx); err != nil {
log.Error("Metrics server error: %v", err)
}
log.Info("Received a SIGINT signal: Gracefully shutting down metrics server")
}()
// Start metrics server
ms := &MetricsServer{
metricsPort: metricsPort,
metricsEndpoint: metricsEndpoint,
stopCh: make(chan struct{}),
cancel: cancel,
}
go func() {
defer close(ms.stopCh)
http.Handle(metricsEndpoint, metric.HandlerFor(m.reg))
log.Info(fmt.Sprintf("Metrics Server: localhost:%s%s", metricsPort, metricsEndpoint))
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Error("Metrics server error: %v", err)
}
}()
return ms
}
func (ms *MetricsServer) Shutdown() {
ms.cancel()
<-ms.stopCh
}
func (m *Metrics) Print(outputFile string) error {
metrics, err := m.reg.Gather()
if err != nil {
return err
}
if outputFile == "" {
// Printout to stdout
fmt.Println("*** Metrics ***")
for _, mf := range metrics {
for _, m := range mf.Metrics {
fmt.Printf("Type: %s, Name: %s, Description: %s, Values: %+v\n", mf.Type.String(), mf.Name, mf.Help, m.Value)
}
}
fmt.Println("***************")
} else {
jsonFile, err := os.Create(outputFile)
if err != nil {
return err
}
defer func() { _ = jsonFile.Close() }()
if err := json.NewEncoder(jsonFile).Encode(metrics); err != nil {
return err
}
}
return nil
}