Files
gocommon/metric/common.go
2025-07-02 11:54:05 +09:00

175 lines
3.6 KiB
Go

package metric
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path"
"runtime"
"sort"
"strings"
"sync/atomic"
"repositories.action2quare.com/ayo/gocommon/logger"
)
type MetricDescription struct {
Key string
Type MetricType
Name string `json:",omitempty"`
Help string `json:",omitempty"`
ConstLabels map[string]string `json:",omitempty"`
}
type Exporter interface {
RegisterMetric(*MetricDescription)
UpdateMetric(string, float64)
}
type MetricPipe struct {
pipe *os.File
}
func (mp MetricPipe) Close() {
if mp.pipe != nil {
mp.pipe.Close()
mp.pipe = nil
}
}
func (mp MetricPipe) writeLine(line string) {
mp.pipe.WriteString(line + "\n")
}
func NewMetricPipe(pipeName string) MetricPipe {
switch runtime.GOOS {
case "linux":
pipeName = "/tmp/" + pipeName
case "windows":
pipeName = `\\.\pipe\` + pipeName
}
f, _ := os.Open(pipeName)
return MetricPipe{
pipe: f,
}
}
type MetricWriter interface {
Add(int64)
Set(int64)
}
type metric_empty struct{}
func (mw *metric_empty) Set(int64) {}
func (mw *metric_empty) Add(int64) {}
var MetricWriterNil = MetricWriter(&metric_empty{})
type metric_int64 struct {
key string
valptr *int64
pipe MetricPipe
}
func (mw *metric_int64) printOut() {
loaded := atomic.LoadInt64(mw.valptr)
mw.pipe.writeLine(fmt.Sprintf("%s:%d", mw.key, loaded))
}
func (mw *metric_int64) Set(newval int64) {
atomic.StoreInt64(mw.valptr, newval)
mw.printOut()
}
func (mw *metric_int64) Add(inc int64) {
atomic.AddInt64(mw.valptr, inc)
mw.printOut()
}
func NewMetric(pipe MetricPipe, mt MetricType, name string, help string, constLabels map[string]string) (writer MetricWriter) {
if !metricEnabled {
return MetricWriterNil
}
if constLabels == nil {
constLabels = map[string]string{}
}
constLabels["pid"] = fmt.Sprintf("%d", os.Getpid())
var disorder []struct {
k string
v string
}
for k, v := range constLabels {
disorder = append(disorder, struct {
k string
v string
}{k: strings.ToLower(k), v: strings.ToLower(v)})
}
sort.Slice(disorder, func(i, j int) bool {
return disorder[i].k < disorder[j].k
})
hash := md5.New()
hash.Write([]byte(strings.ToLower(name)))
for _, d := range disorder {
hash.Write([]byte(d.k))
hash.Write([]byte(d.v))
}
key := hex.EncodeToString(hash.Sum(nil))[:metric_key_size]
temp, _ := json.Marshal(MetricDescription{
Key: key,
Type: mt,
Name: name,
Help: help,
ConstLabels: constLabels,
})
impl := &metric_int64{
key: key,
valptr: new(int64),
pipe: pipe,
}
pipe.writeLine(string(temp))
// writer
return impl
}
var metricEnabled = false
func init() {
if path.Base(os.Args[0]) == "houston" {
logger.Println("metrics are going to be generated for myself(houston)")
metricEnabled = true
return
}
ppid := os.Getppid()
if parent, _ := os.FindProcess(ppid); parent != nil {
filename := fmt.Sprintf(`/proc/%d/stat`, os.Getppid())
if fn, err := os.ReadFile(filename); err == nil {
stats := strings.SplitN(string(fn), " ", 3)
parentname := strings.Trim(stats[1], "()")
if path.Base(parentname) == "houston" {
logger.Println("metrics are going to be generated for houston")
metricEnabled = true
} else {
logger.Println("metrics are NOT going to be generated. parent is not houston :", filename, string(fn))
}
} else {
logger.Println("metrics are NOT going to be generated. ppid proc is missing :", filename)
}
} else {
logger.Println("metrics are NOT going to be generated. parent process is missing. ppid :", ppid)
}
}