github.com/qlik-oss/gopherciser@v0.18.6/metrics/transport.go (about)

     1  package metrics
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	"github.com/prometheus/client_golang/prometheus/promhttp"
    16  	"github.com/prometheus/client_golang/prometheus/push"
    17  
    18  	"github.com/qlik-oss/gopherciser/version"
    19  )
    20  
    21  func setupMetrics(actions []string, apimetrics bool) error {
    22  	if apimetrics { // If not used these api calls would still be registered. Likely no issue
    23  		prometheus.MustRegister(ApiCallDuration)
    24  		prometheus.MustRegister(ApiCallDurationQuantile)
    25  		err := gopherRegistry.Register(ApiCallDuration)
    26  		if err != nil {
    27  			return err
    28  		}
    29  		err = gopherRegistry.Register(ApiCallDurationQuantile)
    30  		if err != nil {
    31  			return err
    32  		}
    33  	}
    34  	prometheus.MustRegister(GopherActions)
    35  	prometheus.MustRegister(GopherWarnings)
    36  	prometheus.MustRegister(GopherErrors)
    37  	prometheus.MustRegister(GopherUsersTotal)
    38  	prometheus.MustRegister(GopherActiveUsers)
    39  	prometheus.MustRegister(GopherResponseTimes)
    40  	prometheus.MustRegister(GopherActionLatencyHist)
    41  	prometheus.MustRegister(BuildInfo)
    42  
    43  	err := gopherRegistry.Register(GopherActions)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	err = gopherRegistry.Register(GopherWarnings)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	err = gopherRegistry.Register(GopherErrors)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	err = gopherRegistry.Register(GopherUsersTotal)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	err = gopherRegistry.Register(GopherActiveUsers)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	err = gopherRegistry.Register(GopherResponseTimes)
    64  	if err != nil {
    65  		return err
    66  	}
    67  	err = gopherRegistry.Register(GopherActionLatencyHist)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	err = gopherRegistry.Register(BuildInfo)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	// Initialize metrics
    77  	for _, action := range actions {
    78  		GopherActions.WithLabelValues("success", action).Add(0)
    79  		GopherActions.WithLabelValues("failure", action).Add(0)
    80  	}
    81  
    82  	BuildInfo.WithLabelValues(version.Version, version.Revision, runtime.Version(), runtime.GOARCH, runtime.GOOS).Set(1)
    83  
    84  	return nil
    85  }
    86  
    87  // PushMetrics handles the constant pushing of metrics to prometheus
    88  func PushMetrics(ctx context.Context, metricsTarget, job string, groupingKeys, actions []string, apiMetrics bool) error {
    89  	err := setupMetrics(actions, apiMetrics)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	_, err = url.Parse(metricsTarget)
    95  	if err != nil {
    96  		return fmt.Errorf("can't parse metricsAddress <%s>, metrics will not be pushed", metricsTarget)
    97  	}
    98  
    99  	var addr = flag.String("push-address", metricsTarget, "The address to push prometheus metrics")
   100  	pusher := push.New(*addr, job).Gatherer(gopherRegistry)
   101  	for _, gk := range groupingKeys {
   102  		kv := strings.SplitN(gk, "=", 2)
   103  		if len(kv) < 2 || len(kv[0]) == 0 {
   104  			return fmt.Errorf("can't parse grouping key %q: must be in 'key=value' form", gk)
   105  		}
   106  		pusher = pusher.Grouping(kv[0], kv[1])
   107  	}
   108  
   109  	//Pushes prometheus metrics every minute
   110  	const interval time.Duration = 1 * time.Minute
   111  	ticker := time.NewTicker(interval)
   112  	go func() {
   113  		for {
   114  			select {
   115  			case <-ticker.C:
   116  				err := pusher.Push()
   117  				if err != nil {
   118  					_, _ = fmt.Fprintf(os.Stderr, "Push error received: %s", err)
   119  				}
   120  			case <-ctx.Done():
   121  				_ = pusher.Push() // push the latest values, but ignore error when shutting down
   122  
   123  				ticker.Stop()
   124  				return
   125  			}
   126  		}
   127  	}()
   128  	return nil
   129  }
   130  
   131  // PullMetrics handle the serving of prometheus metrics on the metrics endpoint
   132  func PullMetrics(ctx context.Context, metricsPort int, actions []string) error {
   133  	err := setupMetrics(actions, true)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	var addr = flag.String("pull-address", fmt.Sprintf(":%d", metricsPort), "The address to listen on for HTTP requests.")
   139  	srv := &http.Server{Addr: *addr}
   140  
   141  	http.Handle("/metrics", promhttp.Handler())
   142  
   143  	go func() {
   144  		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   145  			_, _ = fmt.Fprintf(os.Stderr, "Httpserver: ListenAndServe() error: %s", err)
   146  		}
   147  	}()
   148  
   149  	go func() {
   150  		<-ctx.Done()
   151  		//nolint:staticcheck
   152  		shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
   153  		defer shutdownCancel()
   154  		if err := srv.Shutdown(shutdownCtx); err != nil {
   155  			_, _ = fmt.Fprintf(os.Stderr, "Httpserver: Shutdown() error: %s", err)
   156  		}
   157  	}()
   158  
   159  	return nil
   160  }