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) } }