metric 등록 방법 변경

This commit is contained in:
2023-11-28 22:29:52 +09:00
parent d33fada0a1
commit e5a5240f96
2 changed files with 203 additions and 96 deletions

View File

@ -15,18 +15,7 @@ import (
"repositories.action2quare.com/ayo/gocommon/logger"
)
const (
METRIC_HEAD_INLINE = byte(14)
METRIC_TAIL_INLINE = byte(15)
)
type MetricType int
const (
MetricCounter = MetricType(1)
MetricGuage = MetricType(2)
metric_key_size = 8
)
const metric_value_line_size = 19
type MetricDescription struct {
Key string
@ -36,17 +25,9 @@ type MetricDescription struct {
ConstLabels map[string]string `json:",omitempty"`
}
type writeRequest struct {
key string
valfunc func() float64
}
type metricCollection struct {
writerChan chan *writeRequest
}
var mc = metricCollection{
writerChan: make(chan *writeRequest, 100),
type Exporter interface {
RegisterMetric(*MetricDescription)
UpdateMetric(string, float64)
}
type MetricWriter interface {
@ -56,92 +37,36 @@ type MetricWriter interface {
type metric_empty struct{}
func (mw *metric_empty) Set(newval int64) {}
func (mw *metric_empty) Add(inc int64) {}
func (mw *metric_empty) Set(int64) {}
func (mw *metric_empty) Add(int64) {}
var MetricWriterNil = MetricWriter(&metric_empty{})
type metric_int64 struct {
valptr *int64
key string
writerChan chan *writeRequest
buff [metric_value_line_size]byte
}
func (mw *metric_int64) requestMetricWrite() {
mw.writerChan <- &writeRequest{
key: mw.key,
valfunc: func() float64 { return float64(atomic.LoadInt64(mw.valptr)) },
}
func (mw *metric_int64) printOut() {
binary.LittleEndian.PutUint64(mw.buff[9:], math.Float64bits(float64(atomic.LoadInt64(mw.valptr))))
os.Stdout.Write(mw.buff[:])
}
func (mw *metric_int64) Set(newval int64) {
atomic.StoreInt64(mw.valptr, newval)
mw.requestMetricWrite()
mw.printOut()
}
func (mw *metric_int64) Add(inc int64) {
atomic.AddInt64(mw.valptr, inc)
mw.requestMetricWrite()
mw.printOut()
}
func (mc *metricCollection) metricWriter() {
defer func() {
r := recover()
if r != nil {
logger.Error(r)
}
}()
// head + metric_key_size + 8byte + tail + cr = 19
var buff [19]byte
buff[0] = METRIC_HEAD_INLINE
buff[17] = METRIC_TAIL_INLINE
buff[18] = '\n'
for req := range mc.writerChan {
copy(buff[1:], []byte(req.key))
binary.LittleEndian.PutUint64(buff[9:], math.Float64bits(req.valfunc()))
os.Stdout.Write(buff[:])
}
}
var NewMetric func(MetricType, string, string, map[string]string) MetricWriter
func init() {
NewMetric = func(MetricType, string, string, map[string]string) MetricWriter {
return &metric_empty{}
func NewMetric(mt MetricType, name string, help string, constLabels map[string]string) (writer MetricWriter) {
if !metricEnabled {
return MetricWriterNil
}
if path.Base(os.Args[0]) == "houston" {
logger.Println("metrics are going to be generated for myself(houston)")
go mc.metricWriter()
NewMetric = newMetricImpl
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")
go mc.metricWriter()
NewMetric = newMetricImpl
} 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)
}
}
func newMetricImpl(mt MetricType, name string, help string, constLabels map[string]string) (writer MetricWriter) {
hash := md5.New()
hash.Write([]byte(name))
for k, v := range constLabels {
@ -158,15 +83,62 @@ func newMetricImpl(mt MetricType, name string, help string, constLabels map[stri
ConstLabels: constLabels,
})
writer = &metric_int64{
impl := &metric_int64{
valptr: new(int64),
key: key,
writerChan: mc.writerChan,
}
impl.buff[0] = METRIC_HEAD_INLINE
impl.buff[17] = METRIC_TAIL_INLINE
impl.buff[18] = '\n'
copy(impl.buff[1:], []byte(key))
output := append([]byte{METRIC_HEAD_INLINE}, temp...)
output = append(output, METRIC_TAIL_INLINE, '\n')
os.Stdout.Write(output)
return
// writer
return impl
}
func ReadMetricValue(line []byte) (string, float64) {
if len(line) < 16 {
return "", 0
}
key := string(line[0:8])
valbits := binary.LittleEndian.Uint64(line[8:])
val := math.Float64frombits(valbits)
return key, val
}
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)
}
}

135
metric/prometheus.go Normal file
View File

@ -0,0 +1,135 @@
package metric
import (
"math"
"sync/atomic"
"github.com/prometheus/client_golang/prometheus"
"repositories.action2quare.com/ayo/gocommon/logger"
)
const (
METRIC_HEAD_INLINE = byte(14)
METRIC_TAIL_INLINE = byte(15)
)
type MetricType int
const (
MetricCounter = MetricType(1)
MetricGuage = MetricType(2)
metric_key_size = 8
)
func convertValueType(in MetricType) prometheus.ValueType {
switch in {
case MetricCounter:
return prometheus.CounterValue
case MetricGuage:
return prometheus.GaugeValue
}
return prometheus.UntypedValue
}
type writeRequest struct {
key string
val float64
}
type prometheusMetricDesc struct {
*prometheus.Desc
valueType prometheus.ValueType
valptr *uint64
key string
}
type prometheusExporter struct {
writerChan chan *writeRequest
registerChan chan *prometheusMetricDesc
namespace string
}
func (pe *prometheusExporter) RegisterMetric(nm *MetricDescription) {
pe.registerChan <- &prometheusMetricDesc{
Desc: prometheus.NewDesc(prometheus.BuildFQName(pe.namespace, "", nm.Name), nm.Help, nil, nm.ConstLabels),
valueType: convertValueType(nm.Type),
valptr: new(uint64),
key: nm.Key,
}
}
func (pe *prometheusExporter) UpdateMetric(key string, val float64) {
pe.writerChan <- &writeRequest{key: key, val: val}
}
type prometheusCollector struct {
metrics map[string]*prometheusMetricDesc
}
func (pc *prometheusCollector) Describe(ch chan<- *prometheus.Desc) {
for _, v := range pc.metrics {
ch <- v.Desc
}
}
func (pc *prometheusCollector) Collect(ch chan<- prometheus.Metric) {
for _, v := range pc.metrics {
cm, err := prometheus.NewConstMetric(v.Desc, v.valueType, math.Float64frombits(atomic.LoadUint64(v.valptr)))
if err == nil {
ch <- cm
}
}
}
func (pe *prometheusExporter) loop() {
defer func() {
r := recover()
if r != nil {
logger.Error(r)
}
}()
var collector *prometheusCollector
for {
select {
case req := <-pe.writerChan:
if m := collector.metrics[req.key]; m != nil {
atomic.StoreUint64(m.valptr, math.Float64bits(req.val))
}
case nm := <-pe.registerChan:
var nextmetrics map[string]*prometheusMetricDesc
if collector != nil {
nextmetrics = collector.metrics
prometheus.Unregister(collector)
nextmetrics[nm.key] = nm
} else {
nextmetrics = map[string]*prometheusMetricDesc{
nm.key: nm,
}
}
collector = &prometheusCollector{
metrics: nextmetrics,
}
if err := prometheus.Register(collector); err != nil {
logger.Error("prometheus register err :", *nm, err)
}
}
}
}
func NewPrometheusExport(namespace string) Exporter {
exp := &prometheusExporter{
registerChan: make(chan *prometheusMetricDesc, 10),
writerChan: make(chan *writeRequest, 100),
namespace: namespace,
}
go exp.loop()
return exp
}