How do I add histogram to prometheus exporter in golang? - go

Here is the example of my code.Now I want add histogram in my code.
but I can't find a way to add histogram like this.
Is anybody could help me?
I am able to write histogram sample but I can't add it in my below code
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/log"
"net/http"
)
type fooCollector struct {
fooMetric *prometheus.Desc
}
func newFooCollector(label1 string) *fooCollector {
return &fooCollector{
fooMetric: prometheus.NewDesc("fff_metric",
"Shows whether a foo has occurred in our cluster",
nil, prometheus.Labels{"env":label1},
),
}
}
func (collector *fooCollector) Describe(ch chan<- *prometheus.Desc) {
//Update this section with the each metric you create for a given collector
ch <- collector.fooMetric
}
func (collector *fooCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(collector.fooMetric, prometheus.GaugeValue, 111111)
}
func main() {
prometheus.MustRegister(newFooCollector("dev"))
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":80", nil)
}

finally I learned how histogram works.here is my code
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)
type fooCollector struct {
fooMetric *prometheus.Desc
}
//First,we define the variable of histogram
var (
hbrms_histovec = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "hbrms_histogram",
Help: "hbrms_histogram",
ConstLabels: prometheus.Labels{"constname": "constvalue"},
Buckets: prometheus.ExponentialBuckets(50, 1.3, 15),//50*1.3,15times
},
[]string{"env"},
)
)
func newFooCollector() *fooCollector {
return &fooCollector{
fooMetric: prometheus.NewDesc("fff_metric",
"Shows whether a foo has occurred in our cluster",
nil, nil,
),
}
}
func (collector *fooCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.fooMetric
}
func (collector *fooCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(collector.fooMetric, prometheus.CounterValue, float64(1))
// 2nd,we set metrics in this way instead of write to channel,we just find a way of calling the code below when we visit the url.
hbrms_histovec.WithLabelValues("val1").Observe(float64(10))
}
func main() {
reg := prometheus.NewPedanticRegistry()
reg.MustRegister(newFooCollector())
// finally,we register the metrics "hbrms_histovec" in this way
reg.MustRegister(hbrms_histovec)
gatherers := prometheus.Gatherers{reg}
h := promhttp.HandlerFor(gatherers,
promhttp.HandlerOpts{
ErrorHandling: promhttp.ContinueOnError,
})
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
})
http.ListenAndServe(":80", nil)
}

You can just simply call .Collect(ch) method for each metric(thats both includes description and values). Also you don't need to extend default prometheus route handler - just be careful you don't have collision with default metric names
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)
type fooCollector struct {
fooMetric *prometheus.GaugeVec
hmdrsHistogram *prometheus.HistogramVec
}
func newFooCollector() *fooCollector {
return &fooCollector{
fooMetric: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "fff_metric",
Help: "Shows whether a foo has occurred in our cluster",
}, []string{"country"}),
hmdrsHistogram: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "hbrms_histogram",
Help: "hbrms_histogram",
ConstLabels: prometheus.Labels{"constname": "constvalue"},
Buckets: prometheus.ExponentialBuckets(50, 1.3, 15), //50*1.3,15times
},
[]string{"env"},
),
}
}
func (collector *fooCollector) Describe(ch chan<- *prometheus.Desc) {
// don't need to manually call .Describe() here,
// because description was defined with prometheus.MustRegister method
//collector.fooMetric.Describe(ch)
//collector.hmdrsHistogram.Describe(ch)
}
func (collector *fooCollector) Collect(ch chan<- prometheus.Metric) {
v := 14 // get value from DB/External service/etc
collector.fooMetric.WithLabelValues("qwe").Set(float64(v))
collector.fooMetric.WithLabelValues("qwe").Set(v)
collector.fooMetric.Collect(ch)
collector.hmdrsHistogram.WithLabelValues("val1").Observe(float64(10))
collector.hmdrsHistogram.Collect(ch)
}
func RegisterFooCollector() {
fc := newFooCollector()
prometheus.MustRegister(fc)
}
func main() {
RegisterFooCollector()
// also includes default metrics
http.Handle("/metrics", promhttp.Handler())
err := http.ListenAndServe(":80", nil)
if err != nil {
return
}
}

Related

viper dynamically loading config file has data race

I would like to dynamically load config file and not restart my Go app. I wrote the below files, which runs but has data race.
config.go
package main
import (
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"log"
"sync"
"time"
)
var (
reloadConfig = make(chan string)
reloadConfig2 = make(chan string)
viperLock1 sync.Mutex
viperLock2 sync.Mutex
)
func setUpConfig(file string, merge bool, v *viper.Viper) {
v.AddConfigPath("./")
v.SetConfigName(file)
v.SetConfigType("yml")
if merge {
err1 := v.MergeInConfig()
checkForFatalError("fatal error occurred while reading config file!", err1)
} else {
err := v.ReadInConfig()
checkForFatalError("fatal error occurred while reading config file!", err)
}
log.Println("Initial config value: ", v.GetString("env"))
}
func loadConfigDynamically(configChannel chan string, viperLock *sync.Mutex, vipe *viper.Viper) {
viperLock.Lock()
vipe.OnConfigChange(func(e fsnotify.Event) {
viperLock.Lock()
log.Println("config file changed", e.Name)
environment := vipe.GetString("env")
configChannel <- environment
viperLock.Unlock()
})
viperLock.Unlock()
vipe.WatchConfig()
}
func loadMultipleConfigsDynamically() {
go func() {
time.Sleep(time.Millisecond * 50)
vipe2 := viper.New()
setUpConfig("config_base", false, vipe2)
loadConfigDynamically(reloadConfig2, &viperLock2, vipe2)
time.Sleep(time.Millisecond * 50)
vipe1 := viper.New()
setUpConfig("config", false, vipe1)
loadConfigDynamically(reloadConfig, &viperLock1, vipe1)
}()
}
main.go
package main
import (
log "github.com/sirupsen/logrus"
"os"
"os/signal"
"syscall"
)
var reloadConfigNow = make(chan bool)
var reloadConfigAgain = make(chan bool)
var newConfigValue string
func main() {
loadMultipleConfigsDynamically()
go printUpdatedValueOnly()
go justAnotherGoroutine()
go yetAnotherGoroutine()
shutdownAppGracefully()
}
func printUpdatedValueOnly() {
for {
select {
case updatedValue := <-reloadConfig:
newConfigValue = updatedValue
log.Println("dynamically loaded config value: ", updatedValue)
reloadConfigNow <-true
reloadConfigAgain <-true
case updatedValue1 := <-reloadConfig2:
newConfigValue = updatedValue1
log.Println("dynamically loaded config value: ", updatedValue1)
reloadConfigNow <-true
reloadConfigAgain <-true
default:
}
}
}
func justAnotherGoroutine(){
existingConfigValue := ""
for {
select {
case <-reloadConfigNow:
existingConfigValue = newConfigValue
log.Println("justAnotherGoroutine: ", existingConfigValue)
default:
}
}
}
func yetAnotherGoroutine() {
existingConfigValue := ""
for {
select {
case <-reloadConfigAgain:
existingConfigValue = newConfigValue
log.Println("yetAnotherGoroutine: ", existingConfigValue)
default:
}
}
}
func checkForFatalError(errorMsg string, err error) {
if err != nil {
log.Fatal(errorMsg, err)
}
}
func shutdownAppGracefully() {
killSignal := make(chan os.Signal, 1)
signal.Notify(killSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
k := <-killSignal
log.Info("OS Interrupt Signal received, application is shutting down!")
logSystemInterruptType(k)
}
func logSystemInterruptType(osInterrupt os.Signal) {
switch osInterrupt {
case syscall.SIGHUP:
log.Info("SIGHUP")
case syscall.SIGINT:
log.Info("SIGINT")
case syscall.SIGTERM:
log.Info("SIGTERM")
case syscall.SIGQUIT:
log.Info("SIGQUIT")
default:
log.Info("Unknown OS Interrupt")
}
}
config.yml
env : "LOCAL"
config_base.yml
env : "dev15"
go.mod
module reload_config
go 1.16
require (
github.com/fsnotify/fsnotify v1.4.9
github.com/spf13/viper v1.8.1
)
I learned recently that viper is not thread safe and hence I need to wrap it with mutex. I tried to do the same. In config.go file, func loadConfigDynamically, where I set OnConfigChange is the data race for read. And in the same function at the same line is previous write data race. I run the above package with
go run -race reload_config
And change the value of env in the config.yml to test if the config file is loading dynamically.This data race only occurs for the very first time config reloading dynamically. For subsequent times, it works just fine.
You lock viperLock called vipe.WatchConfig() and set vipe.OnConfigChange with a function it is also locking viperLock.
Because you already called vipe.WatchConfig() and then it started to call vipe.OnConfigChange in separate go routine. it is also try to acquire the same lock. That's why there is a race condition.
Call vipe.WatchConfig() after setting the vipe.OnConfigChange and after release the lock.
It should be corrected as below.
func loadConfigDynamically() {
go func() {
time.Sleep(time.Second)
viperLock.Lock()
vipe.OnConfigChange(func(e fsnotify.Event) {
viperLock.Lock()
log.Println("config file changed", e.Name)
environment := vipe.GetString("env")
reloadConfig <- environment
viperLock.Unlock()
})
viperLock.Unlock()
vipe.WatchConfig() //this starting call vipe.OnConfigChange
}()
}
It could be that go thinks that a variable is being modified and accessed by two goroutines at the same time and that there is no lock on the modified and accessed places.
Something like the following example:
package main
import (
"time"
)
type Foo struct {
f func(string)
}
func (f *Foo) Watch() {
go func() {
for {
time.Sleep(time.Second * 2)
if f.f != nil {
f.f("hello world")
}
}
}()
}
func (f *Foo) SetF(fun func(string)) {
f.f = fun
}
func main() {
f := Foo{}
f.Watch()
f.SetF(func(s string) {
})
time.Sleep(time.Second * 5)
}
It has a data race. If I put the same lock on both the modified and read places there will be no data race:
package main
import (
"sync"
"time"
)
var lock sync.Mutex
type Foo struct {
f func(string)
}
func (f *Foo) Watch() {
go func() {
for {
time.Sleep(time.Second * 2)
lock.Lock() // read places
if f.f != nil {
f.f("hello world")
}
lock.Unlock()
}
}()
}
func (f *Foo) SetF(fun func(string)) {
f.f = fun
}
func main() {
f := Foo{}
f.Watch()
lock.Lock() // write places
f.SetF(func(s string) {
})
lock.Unlock()
time.Sleep(time.Second * 5)
}
Or to eliminate the possibility of two goroutines reading and writing at the same time would work fine:
func main() {
f := Foo{}
f.SetF(func(s string) {
})
f.Watch()
time.Sleep(time.Second * 5)
}

Finding all functions of certain type

Is there a way to list out all functions that uses/returns a specific type?
For example: I'm interested to use the following function.
func ListenAndServe(addr string, handler Handler) error
How can I find out all functions (across all Go packages) that can return a Handler?
I'd write an analysis tool using the x/tools/go/analysis framework. Here's a rough sketch that you can run on any module (it uses go/packages underneath so it fully supports modules):
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
)
var RtAnalysis = &analysis.Analyzer{
Name: "rtanalysis",
Doc: "finds functions by return type",
Run: run,
}
func main() {
singlechecker.Main(RtAnalysis)
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
if funcTy, ok := n.(*ast.FuncType); ok {
if funcTy.Results != nil {
for _, fl := range funcTy.Results.List {
if tv, ok := pass.TypesInfo.Types[fl.Type]; ok {
if tv.Type.String() == "net/http.Handler" {
ns := nodeString(funcTy, pass.Fset)
fmt.Printf("%s has return of type net/http.Handler\n", ns)
}
}
}
}
}
return true
})
}
return nil, nil
}
// nodeString formats a syntax tree in the style of gofmt.
func nodeString(n ast.Node, fset *token.FileSet) string {
var buf bytes.Buffer
format.Node(&buf, fset, n)
return buf.String()
}

Override receiver type in test and dependency-inject into logic

Using Go v1.14.3, I'm trying to do the following:
package main
import (
"os"
"github.com/mihaigalos/go-bar/bar"
)
var progressBar bar.Bar
func (*ProgressHandler) New(begin int, end int) {
progressBar.New(begin, end)
}
func main() {
var progressHandler ProgressHandler
send(&progressHandler)
}
So far so good. But when I test, I have no need to see any progressbar, hence my ProgressHandler can be empty, and I want to dependency-inject this object into send().
Inside it will call my specified New().
package main
import (
"testing"
)
func (*ProgressHandler) New(int, int) {
}
func TestSendWorks_whenTypical(t *testing.T) {
expected := true
var progressHandler ProgressHandler
actual := send(&progressHandler)
if actual != expected {
t.Errorf("No Match: %b != %b", actual, expected)
}
}
When I compile this, I get a name collision with the handlers defined in main:
integration_typical_test.go:23:25: (*ProgressHandler).New redeclared in this block
previous declaration at main.go:15:6
I've tried changing the name of the package to something different. The same error there.
How can I improve here?
Use an interface.
type ProgressBar interface {
New(int, int)
}
var progressBar bar.Bar
// New implements ProgressBar
func (*ProgressHandler) New(begin int, end int) {
progressBar.New(begin, end)
}
// change *ProgressHandler to ProgressBar
func send(pb ProgressBar) {
// ...
}
func main() {
var progressHandler ProgressHandler
send(&progressHandler)
}
package main
import (
"testing"
)
type fakeProgressHandler struct{}
func (*fakeProgressHandler) New(int, int) {
}
func TestSendWorks_whenTypical(t *testing.T) {
expected := true
var progressHandler fakeProgressHandler
actual := send(&progressHandler)
if actual != expected {
t.Errorf("No Match: %b != %b", actual, expected)
}
}
Handle nil receiver in the handler.
package main
import (
"os"
"github.com/mihaigalos/go-bar/bar"
)
var progressBar bar.Bar
func (h *ProgressHandler) New(begin int, end int) {
if h == nil {
return
}
progressBar.New(begin, end)
}
func main() {
var progressHandler ProgressHandler
send(&progressHandler)
}
package main
import (
"testing"
)
func TestSendWorks_whenTypical(t *testing.T) {
expected := true
actual := send(nil)
if actual != expected {
t.Errorf("No Match: %b != %b", actual, expected)
}
}

Prometheus client cleaning up counter prematurely?

I am trying to write a program which exposes prometheus metrics.
It is a simple program, where I want to increment a counter for every time my "run" method is called on my struct.
import (
"log"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type myStruct struct {
errorCount prometheus.Counter
}
func (s myStruct) initialize() {
s.errorCount = prometheus.NewCounter(prometheus.CounterOpts{
Name: "my_counter",
Help: "sample prometheus counter",
})
}
func (s myStruct) run() {
s.errorCount.Add(1)
}
func main() {
s := new(myStruct)
s.initialize()
http.Handle("/metrics", promhttp.Handler())
go func() {
for {
s.run()
time.Sleep(time.Second)
}
}()
log.Fatal(http.ListenAndServe(":8080", nil))
}
Above code fails with a "Failed to continue - bad access" error, every time I try to increment the counter. i.e. at this line
s.errorCount.Inc()
I am unable to determine why the counter suddenly disappears from memory (if I'm understanding the error message correctly).
I am determine if i am missing something fundamental w.r.t. Go, or am I using the prometheus client library incorrectly.
In initialise() s is being passed by value which means that in main() s.errorCount is nil.
Just change the declaration of initialise (and run) to take a pointer.
func (s *myStruct) initialize() {
...
A few more suggestions you might like to try:
func init() {
go func() {
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}()
}
type myStruct struct {
errorCount prometheus.Counter
}
func NewMyStruct() *myStruct {
return &myStruct {
errorCount: prometheus.NewCounter(prometheus.CounterOpts {
Name: "my_counter",
Help: "sample prometheus counter",
}),
}
}
func (s *myStruct) run() {
s.errorCount.Add(1)
}
func main() {
s := NewMyStruct()
go func() {
for {
s.run()
time.Sleep(time.Second)
}
}()
// ... OR select{}
}

How to get value property in console if use import func?

I need to get property value:
telegram_token: "telegramtoken"
other_token: "othertoken"
But if I do Init() of import api and initialize function in func main() I don't get property value.
Why?
Thanks!
This is works:
package main
import (
"fmt"
"github.com/go-yaml/yaml"
)
var (
cfg Config
configData = []byte(`api:
telegram_token: "telegramtoken"
other_token: "othertoken"`)
)
type Config struct {
API ConfigAPI `yaml:"api"`
}
type ConfigAPI struct {
TelegramToken string `yaml:"telegram_token"`
OtherToken string `yaml:"other_token"`
}
func (c *Config) parse() {
err := yaml.Unmarshal(configData, c)
if err != nil {
fmt.Println(err.Error())
}
}
func Init() {
cfg.parse()
fmt.Printf("%+v\n", cfg)
}
func main() {
Init()
}
Console:
{API:{TelegramToken:telegramtoken OtherToken:othertoken}}
Doesn't work:
package api
import (
"fmt"
"github.com/go-yaml/yaml"
)
var (
cfg Config
configData = []byte(`api:
telegram_token: "telegramtoken"
waves_token: "wavestoken"`)
)
type Config struct {
API ConfigAPI `yaml:"api"`
}
type ConfigAPI struct {
TelegramToken string `yaml:"telegram_token"`
WavesToken string `yaml:"waves_token"`
}
func (c *Config) parse() {
err := yaml.Unmarshal(configData, c)
if err != nil {
fmt.Println(err.Error())
}
}
func Init() {
cfg.parse()
fmt.Printf("%+v\n", cfg)
}
package main, see on api.Init()
// The daemon that starts the API in background process.
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/julienschmidt/httprouter"
api "github.com/krypton-code/waves-bot/pkg/api"
)
var (
host = "https://api.telegram.org/bot"
method = "/getMe"
)
// Index - home page.
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "<h1>Server of TelegaBot for Waves Platform is running!</h1>\n")
}
func main() {
api.Init()
router := httprouter.New()
router.GET("/", Index)
s := &http.Server{
Addr: ":8089",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Printf("\nApp listening on port%s. Go to http://localhost:8089/", s.Addr)
log.Fatal(s.ListenAndServe())
}
Console:
{API:{TelegramToken: WavesToken:}}
Modify the YAML to match the expected structure by indenting the token fields:
configData = []byte(`api:
telegram_token: "telegramtoken"
waves_token: "wavestoken"`)
)

Resources