-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Iusupov Anton
committed
Jul 30, 2024
1 parent
40ed2d8
commit a34181b
Showing
9 changed files
with
380 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package metrics | ||
|
||
import ( | ||
"net/http" | ||
"sync/atomic" | ||
|
||
"github.com/gateway-fm/scriptorium/clog" | ||
) | ||
|
||
type HealthChecker struct { | ||
isReady atomic.Value | ||
isHealthy atomic.Value | ||
logger clog.CLog | ||
} | ||
|
||
func NewHealthChecker(logger clog.CLog) *HealthChecker { | ||
hc := &HealthChecker{ | ||
logger: logger, | ||
} | ||
hc.isReady.Store(false) | ||
hc.isHealthy.Store(true) | ||
return hc | ||
} | ||
|
||
func (hc *HealthChecker) SetReady(ready bool) { | ||
hc.isReady.Store(ready) | ||
} | ||
|
||
func (hc *HealthChecker) SetHealthy(healthy bool) { | ||
hc.isHealthy.Store(healthy) | ||
} | ||
|
||
func (hc *HealthChecker) LivenessHandler(w http.ResponseWriter, r *http.Request) { | ||
if hc.isHealthy.Load().(bool) { | ||
w.WriteHeader(http.StatusOK) | ||
_, err := w.Write([]byte("ok")) | ||
if err != nil { | ||
hc.logger.ErrorCtx(r.Context(), err, "Failed to write liveness response") | ||
} | ||
} else { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
_, err := w.Write([]byte("unhealthy")) | ||
if err != nil { | ||
hc.logger.ErrorCtx(r.Context(), err, "Failed to write liveness response") | ||
} | ||
} | ||
} | ||
|
||
func (hc *HealthChecker) ReadinessHandler(w http.ResponseWriter, r *http.Request) { | ||
if hc.isReady.Load().(bool) { | ||
w.WriteHeader(http.StatusOK) | ||
_, err := w.Write([]byte("ok")) | ||
if err != nil { | ||
hc.logger.ErrorCtx(r.Context(), err, "Failed to write readiness response") | ||
} | ||
} else { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
_, err := w.Write([]byte("not ready")) | ||
if err != nil { | ||
hc.logger.ErrorCtx(r.Context(), err, "Failed to write readiness response") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package metrics | ||
|
||
import "github.com/prometheus/client_golang/prometheus" | ||
|
||
type Registry interface { | ||
Inc(name string) | ||
RecordDuration(name string, labels []string) *prometheus.HistogramVec | ||
PrometheusRegistry() *prometheus.Registry | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package metrics | ||
|
||
import ( | ||
"strings" | ||
"sync" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/collectors" | ||
) | ||
|
||
type registry struct { | ||
Subsystem string | ||
Namespace string | ||
PromRegistry *prometheus.Registry | ||
|
||
metricsMu sync.Mutex | ||
counters map[string]prometheus.Counter | ||
histograms map[string]*prometheus.HistogramVec | ||
} | ||
|
||
func NewRegistry(subsystem, namespace string) Registry { | ||
r := ®istry{ | ||
Subsystem: subsystem, | ||
Namespace: namespace, | ||
PromRegistry: prometheus.NewRegistry(), | ||
counters: make(map[string]prometheus.Counter), | ||
histograms: make(map[string]*prometheus.HistogramVec), | ||
} | ||
|
||
registerMetrics(r) | ||
|
||
return r | ||
} | ||
|
||
func (r *registry) sanitizeMetricName(name string) string { | ||
return strings.ReplaceAll(strings.ReplaceAll(name, ".", "_"), "-", "_") | ||
} | ||
|
||
func (r *registry) Inc(name string) { | ||
r.metricsMu.Lock() | ||
defer r.metricsMu.Unlock() | ||
|
||
sanitized := r.sanitizeMetricName(name) | ||
counter, exists := r.counters[sanitized] | ||
if !exists { | ||
counter = prometheus.NewCounter(prometheus.CounterOpts{ | ||
Subsystem: r.Subsystem, | ||
Namespace: r.Namespace, | ||
Name: sanitized, | ||
}) | ||
r.PromRegistry.MustRegister(counter) | ||
r.counters[sanitized] = counter | ||
} | ||
counter.Inc() | ||
} | ||
|
||
func (r *registry) RecordDuration(name string, labels []string) *prometheus.HistogramVec { | ||
r.metricsMu.Lock() | ||
defer r.metricsMu.Unlock() | ||
|
||
sanitized := r.sanitizeMetricName(name) | ||
histogram, exists := r.histograms[sanitized] | ||
if !exists { | ||
histogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ | ||
Subsystem: r.Subsystem, | ||
Namespace: r.Namespace, | ||
Name: sanitized, | ||
Buckets: prometheus.DefBuckets, | ||
}, labels) | ||
r.PromRegistry.MustRegister(histogram) | ||
r.histograms[sanitized] = histogram | ||
} | ||
return histogram | ||
} | ||
|
||
func (r *registry) PrometheusRegistry() *prometheus.Registry { | ||
return r.PromRegistry | ||
} | ||
|
||
func registerMetrics(registry *registry) { | ||
registry.PromRegistry.MustRegister( | ||
collectors.NewGoCollector( | ||
collectors.WithGoCollectorMemStatsMetricsDisabled(), | ||
collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsScheduler), | ||
)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package metrics | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
) | ||
|
||
type ( | ||
SeriesType string | ||
|
||
Series struct { | ||
st SeriesType | ||
name string | ||
operation string | ||
} | ||
|
||
seriesContextKey struct{} | ||
) | ||
|
||
func (st SeriesType) String() string { | ||
return string(st) | ||
} | ||
|
||
const ( | ||
SeriesTypeRPCHandler SeriesType = "rpc_handler" | ||
SeriesTypeApiHandler SeriesType = "api_handler" | ||
SeriesTypeUseCase SeriesType = "use_case" | ||
SeriesTypeClient SeriesType = "client" | ||
SeriesTypeDB SeriesType = "postgres" | ||
SeriesTypeDatabusConsumer SeriesType = "databus_consumer" | ||
) | ||
|
||
func NewSeries(st SeriesType, name string) Series { | ||
return Series{ | ||
st: st, | ||
name: name, | ||
operation: "undefined", | ||
} | ||
} | ||
|
||
func FromContext(ctx context.Context) Series { | ||
series, ok := ctx.Value(seriesContextKey{}).(Series) | ||
if !ok { | ||
return Series{} | ||
} | ||
|
||
return series | ||
} | ||
|
||
func (s Series) WithOperation(ctx context.Context, operation string) (context.Context, Series) { | ||
series := FromContext(ctx) | ||
|
||
if s.st == series.st && | ||
s.name == series.name { | ||
series = Series{ | ||
st: s.st, | ||
name: s.name, | ||
operation: series.appendOperation(operation), | ||
} | ||
|
||
return series.ToContext(ctx), series | ||
} | ||
|
||
series = Series{ | ||
st: s.st, | ||
name: s.name, | ||
operation: operation, | ||
} | ||
|
||
return series.ToContext(ctx), series | ||
} | ||
|
||
func (s Series) ToContext(ctx context.Context) context.Context { | ||
return context.WithValue(ctx, seriesContextKey{}, s) | ||
} | ||
|
||
func (s Series) Success() string { | ||
return fmt.Sprintf("%s_%s_%s_success", s.st.String(), s.name, s.operation) | ||
} | ||
|
||
func (s Series) Error(errCode string) string { | ||
return fmt.Sprintf("%s.%s.%s.error.%s", s.st.String(), s.name, s.operation, errCode) | ||
} | ||
|
||
func (s Series) Duration() string { | ||
return fmt.Sprintf("%s.%s.%s.duration", s.st.String(), s.name, s.operation) | ||
} | ||
|
||
func (s Series) Info(code string) string { | ||
return fmt.Sprintf("%s.%s.%s.info.%s", s.st.String(), s.name, s.operation, code) | ||
} | ||
|
||
func (s Series) Operation() string { | ||
return s.operation | ||
} | ||
|
||
func (s Series) appendOperation(operation string) string { | ||
return s.operation + "_" + operation | ||
} |
Oops, something went wrong.