Skip to content

Instantly share code, notes, and snippets.

@ionling
Created November 13, 2023 06:39
Show Gist options
  • Save ionling/041cab12e8ebf4efce744fac230f524c to your computer and use it in GitHub Desktop.
Save ionling/041cab12e8ebf4efce744fac230f524c to your computer and use it in GitHub Desktop.
Service health checkers
package health
import (
"context"
khttp "github.com/go-kratos/kratos/v2/transport/http"
)
type Health struct {
checkers []Checker
}
type Result struct {
Components []Component
}
type Component struct {
Status string
Result any
}
type Checker interface {
Check(context.Context) (any, error)
}
func New() *Health {
return &Health{}
}
func (h *Health) Add(name string, c Checker) *Health {
h.checkers = append(h.checkers, c)
return h
}
func (h *Health) KratosHandler() khttp.HandlerFunc {
return func(ctx khttp.Context) (err error) {
var res = Result{}
for _, c := range h.checkers {
status := "OK"
r, err := c.Check(ctx)
if err != nil {
status = err.Error()
}
res.Components = append(res.Components, Component{
Status: status,
Result: r,
})
}
return ctx.JSON(200, res)
}
}
package sqlh
import (
"context"
"database/sql"
"fmt"
"strconv"
"time"
"health"
)
type checker struct {
db *sql.DB
}
func NewChecker(db *sql.DB) health.Checker {
return &checker{
db: db,
}
}
type PGConf struct {
Name string
Setting string
}
type PGResult struct {
// Starts the autovacuum subprocess.
AutoVacuum bool
// Sets the client's character set encoding.
ClientEncoding string
// Sets the delay in microseconds between transaction commit and flushing WAL to disk.
CommitDelay int
// Sets the minimum concurrent open transactions before performing commit_delay.
CommitSiblings int
// Sets the server's data directory.
DataDirectory string
// Sets the time to wait on a lock before checking for deadlock.
DeadlockTimeout time.Duration
// Shows the maximum number of index keys.
MaxIndexKeys int
// Sets the maximum stack depth, in kilobytes.
MaxStackDepth string
// Sets the WAL size that triggers a checkpoint.
MaxWALSize string
// Sets the TCP port the server listens on.
Port int
// Shows the server version.
Version string
}
func (c *checker) Check(ctx context.Context) (any, error) {
rows, err := c.db.QueryContext(ctx, "SHOW ALL")
if err != nil {
return nil, fmt.Errorf("query: %w", err)
}
var confs []PGConf
for rows.Next() {
var conf PGConf
if err := rows.Scan(&conf.Name, &conf.Setting); err != nil {
return nil, fmt.Errorf("scan: %w", err)
}
confs = append(confs, conf)
}
var res PGResult
for _, conf := range confs {
switch v := conf.Setting; conf.Name {
case "autovacuum":
res.AutoVacuum = v == "on"
case "client_encoding":
res.ClientEncoding = v
case "commit_delay":
res.CommitDelay, err = strconv.Atoi(v)
case "commit_siblings":
res.CommitSiblings, err = strconv.Atoi(v)
case "data_directory":
res.DataDirectory = v
case "deadlock_timeout":
res.DeadlockTimeout, err = time.ParseDuration(v)
case "max_index_keys":
case "max_stack_depth":
res.MaxStackDepth = v
case "max_wal_size":
res.MaxStackDepth = v
case "port":
res.Port, err = strconv.Atoi(v)
case "server_version":
res.Version = v
}
if err != nil {
// TODO
}
}
return nil, nil
}
package redish
import (
"context"
"fmt"
"strings"
"github.com/go-redis/redis/v8"
"github.com/mitchellh/mapstructure"
"health"
)
type checker struct {
c *redis.Client
}
func NewChecker(c *redis.Client) health.Checker {
return &checker{
c: c,
}
}
type Result struct {
Version string `mapstructure:"redis_version"`
OS string `mapstructure:"os"`
UptimeDays int `mapstructure:"uptime_in_days"`
ConnectedClients int `mapstructure:"connected_clients"`
UsedMemory string `mapstructure:"used_memory_human"`
AOFEnabled bool `mapstructure:"aof_enabled"`
}
func Parse(s string) (res *Result, err error) {
m := map[string]string{}
for _, line := range strings.Split(s, "\r\n") {
if strings.HasPrefix(line, "#") {
continue
}
kv := strings.SplitN(line, ":", 2)
if len(kv) != 2 {
continue
}
m[kv[0]] = kv[1]
}
res = new(Result)
err = mapstructure.WeakDecode(m, res)
return
}
func (c *checker) Check(ctx context.Context) (res any, err error) {
cmd := c.c.Info(ctx)
if err := cmd.Err(); err != nil {
return nil, fmt.Errorf("get: %w", err)
}
res, err = Parse(cmd.Val())
if err != nil {
return nil, fmt.Errorf("parse: %w", err)
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment