package metric import ( "crypto/md5" "encoding/binary" "encoding/hex" "encoding/json" "fmt" "math" "os" "path" "sort" "strings" "sync/atomic" "repositories.action2quare.com/ayo/gocommon/logger" ) const metric_value_line_size = 19 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 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 { valptr *int64 buff [metric_value_line_size]byte } 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.printOut() } func (mw *metric_int64) Add(inc int64) { atomic.AddInt64(mw.valptr, inc) mw.printOut() } func NewMetric(mt MetricType, name string, help string, constLabels map[string]string) (writer MetricWriter) { if !metricEnabled { return MetricWriterNil } 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{ 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) // 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) } }