From e5a5240f96d85ab584a4832c52da241928f14f11 Mon Sep 17 00:00:00 2001 From: mountain Date: Tue, 28 Nov 2023 22:29:52 +0900 Subject: [PATCH] =?UTF-8?q?metric=20=EB=93=B1=EB=A1=9D=20=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metric/{metric.go => common.go} | 164 +++++++++++++------------------- metric/prometheus.go | 135 ++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 96 deletions(-) rename metric/{metric.go => common.go} (55%) create mode 100644 metric/prometheus.go diff --git a/metric/metric.go b/metric/common.go similarity index 55% rename from metric/metric.go rename to metric/common.go index 4c69c54..5ca63e6 100644 --- a/metric/metric.go +++ b/metric/common.go @@ -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 + valptr *int64 + 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{ - valptr: new(int64), - key: key, - writerChan: mc.writerChan, + impl := &metric_int64{ + valptr: new(int64), } + 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) + } } diff --git a/metric/prometheus.go b/metric/prometheus.go new file mode 100644 index 0000000..32d4d81 --- /dev/null +++ b/metric/prometheus.go @@ -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 +}