Some confusion of Go `net/http` under high concurrency - go

Some confusion under high concurrency.
I use wrk to test Gin, there's someting uncertainty. Gin seems doesn't concurrency safe.
package main
import (
"fmt"
"sync/atomic"
"github.com/gin-gonic/gin"
)
var count int64 = 0
func Test(c *gin.Context) {
atomic.AddInt64(&count, 1)
fmt.Println(count)
c.String(200, "success")
}
func main() {
gin.SetMode(gin.DebugMode)
router := gin.New()
router.GET("test", Test)
router.Run(":8080")
}
Test shell code
wrk -t50 -c50 -d 1s http://localhost:8080/test
Gin output duplicate data
========update========
even if the print code like this.
countCopy := count
go func() {
fmt.Println(countCopy)
}()
I also use ab test it, the same problem.
========update========
The same with net/http, still has duplicate data.
package main
import (
"fmt"
"net/http"
"sync/atomic"
)
var count int64 = 0
func Test(w http.ResponseWriter, req *http.Request) {
atomic.AddInt64(&count, 1)
fmt.Println(count)
w.Write([]byte("success"))
}
func main() {
http.HandleFunc("/test", Test)
http.ListenAndServe(":8080", nil)
}
I try to use log package, which is safe from concurrent goroutines . The same.
log.Println(countCopy)

You have to use the returned value from atomic.AddInt64(&count, 1), as count can change before you have a chance to print it:
func Test(c *gin.Context) {
current := atomic.AddInt64(&count, 1)
fmt.Println(current)
c.String(200, "success")
}

Related

invalid receiver error when using Gin Gonic framework in Go

I am trying to use an external (non anonymous) function in the routing of my Gin based web server as shown below:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/hi/", Hi)
router.Run(":8080")
}
func (c *gin.Context) Hi() {
c.String(http.StatusOK, "Hello")
}
But I get 2 errors:
./main.go:13:23: undefined: Hi
./main.go:18:6: cannot define new methods on non-local type gin.Context
I am wondering how I can use anonymous functions in my endpoint handlers with gin gonic? All the documentation I've found so far uses anonymous functions.
Thanks!
You can only define a new method for a type in the same package declaring that type. That is, you cannot add a new method to gin.Context.
You should do:
func Hi(c *gin.Context) {
...
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/hi", hi)
var n Node
router.GET("/hello", n.hello)
router.GET("/extra", func(ctx *gin.Context) {
n.extra(ctx, "surprise~")
})
router.Run(":8080")
}
func hi(c *gin.Context) {
c.String(200, "hi")
}
type Node struct{}
func (n Node) hello(c *gin.Context) {
c.String(200, "world")
}
func (n Node) extra(c *gin.Context, data interface{}) {
c.String(200, "%v", data)
}

How can I pass a var between routes funcs and change it in every controller?

I want to pass var and change it globally in every controller between routes so if controller1 change var to "as" I want controller2 to use the new value of the var = "as". How can I do it?
something like this
main.go:
var test []string
func NewRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.GET("/ping", gets(test))
r.Run(":6030")
return r
}
controller.go:
func gets(test) gin.HandlerFunc {
fn := func(c *gin.Context){
// Here I want to change my var (test) and I want to change it globally so if any another function use it I want to use the new value I just change it here
}
}
The comment of mkopriva already give you a solution on how to using mutex.
However, since golang promote share memory by communicating, I propose another solution using channel.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
r := NewRouter()
r.Run(":6030")
}
var test = []string{"abc"}
func NewRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
updateChan := make(chan []string, 0)
r.GET("/", gets(updateChan))
r.GET("/mn", er(updateChan))
return r
}
func gets(updateChan chan []string) gin.HandlerFunc {
return func(c *gin.Context){
fmt.Println("before update in get, test=",test)
newTest := []string{"def"}
updateChan <- newTest
}
}
func er(updateChan chan []string) gin.HandlerFunc {
return func(c *gin.Context){
test = <-updateChan
fmt.Println("get a new value for test in er, test=",test)
}
}
With an unbuffered channel updateChan, test will always be update with the latest input pushed to updateChan channel. And no data race will happen here.
Of course this is just a simple demonstration and there are lot more stuff to make a complete HTTP server with gin. But I limit the scope of the demonstration to channel usage only.

How to use Gorilla Sessions in Golang in different packages

I have main package (main.go):
package main
import (
"github.com/gorilla/sessions"
...
)
func main() {
var store = sessions.NewCookieStore([]byte("secret"))
http.HandleFunc("/", routes.Index)
http.ListenAndServe(":8080", nil)
...
And I have another package (index.go):
package routes
import (
"github.com/gorilla/sessions"
..
)
func Index(res http.ResponseWriter, req *http.Request) {
session, _ := store.Get(req, "session-name")
...
How can I get session value from another package? Or should I pass it to my Handler (if yes how to do it?).
I am new in Golang. Please, help.
I generally wrap my dependencies in their own package, which let's me abstract away some of the common things I do. For sessions, I usually use the same session name most of the time, so I would usually have something like this:
package sessions
import (
"os"
gsessions "github.com/gorilla/sessions"
)
var store = gsessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
func Get(req *http.Request) (*gsessions.Session, error) {
return store.Get(req, "default-session-name")
}
func GetNamed(req *http.Request, name string) (*gsessions.Session, error) {
return store.Get(req, name)
}
And then anywhere else you can just import your own sessions package:
import (
"net/http"
"github.com/yourpackage/sessions"
)
func Index(rw http.ResponseWriter, r *http.Request) {
session, err := sessions.Get(r)
if err != nil {
panic(err)
}
session.Values["test"] = "test"
session.Save(r, rw)
}
Even better would be to only return an interface of some sort from sessions, and completely wrap the gorilla/sessions so that aren't dependent on it anywhere except for your own sessions package.

How to update metric values in prometheus exporter (golang)

I'm starting to write an own prometheus exporter using golang.
I think I got the basics but I don't know what to do exactly to get the value of the metric up to date. Using Set only does it once.
It is not changing on runtime.
What I have so far:
package main
import (
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"time"
"io/ioutil"
"github.com/tidwall/gjson"
"strconv"
)
var (
sidekiqProcessed = setGaugeMetric("sidekiq_processed", "Sidekiq Processed", "lable", "lablevalue")
)
func setGaugeMetric(name string, help string, label string, labelvalue string) (prometheusGauge prometheus.Gauge) {
var (
gaugeMetric = prometheus.NewGauge(prometheus.GaugeOpts{
Name: name,
Help: help,
ConstLabels: prometheus.Labels{label: labelvalue},
})
)
return gaugeMetric
}
func getSidekiqProcessed() (sidekiq float64) {
body := getContent("http://example.com/sidekiq/stats")
processed := gjson.Get(body, "sidekiq.processed")
conv, err := strconv.ParseFloat(processed.String(), 64)
if err != nil {
log.Fatal(err)
}
return conv
}
func getContent(url string) (body string) {
httpClient := &http.Client{Timeout: 10 * time.Second}
res, err := httpClient.Get(url)
if err != nil {
log.Fatal(err)
}
content, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
return string(content)
}
func init() {
prometheus.MustRegister(sidekiqProcessed)
}
func main() {
sidekiqProcessed.Set(getSidekiqProcessed())
// The Handler function provides a default handler to expose metrics
// via an HTTP server. "/metrics" is the usual endpoint for that.
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
Read something about Collector but have no clue how to implement it.
Can somebody help me to complete/correct my code so that the value of the metric also updates at runtime?
Here is an example of custom collector (from https://www.robustperception.io/setting-a-prometheus-counter):
package main
import "github.com/prometheus/client_golang/prometheus"
type MyCollector struct {
counterDesc *prometheus.Desc
}
func (c *MyCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.counterDesc
}
func (c *MyCollector) Collect(ch chan<- prometheus.Metric) {
value := 1.0 // Your code to fetch the counter value goes here.
ch <- prometheus.MustNewConstMetric(
c.counterDesc,
prometheus.CounterValue,
value,
)
}
func NewMyCollector() *MyCollector {
return &MyCollector{
counterDesc: prometheus.NewDesc("my_counter_total", "Help string", nil, nil),
}
}
// To hook in the collector: prometheus.MustRegister(NewMyCollector())
You probably want to implement a collector instead, and run the http request when the Prometheus server scrapes. See the best practices.
When implementing the collector for your exporter, you should never use the usual direct instrumentation approach and then update the metrics on each scrape.
Rather create new metrics each time. In Go this is done with MustNewConstMetric in your Update() method. For Python see https://github.com/prometheus/client_python#custom-collectors and for Java generate a List in your collect method, see StandardExports.java for an example.
The github.com/prometheus/client_golang library may be non-trivial to use when writing Prometheus exporters in Go. Try https://pkg.go.dev/github.com/VictoriaMetrics/metrics library instead. It is much easier to use in general. See the following code as an example, which allows dynamically updating sidekiq_processed metric with the given label:
import (
"fmt"
"github.com/VictoriaMetrics/metrics"
)
// UpdateSidekiqProcessed updates `sidekiq_processed{label="<labelValue>"}` metric to the given value
func UpdateSidekiqProcessed(labelValue string, value float64) {
metricName := fmt.Sprintf("sidekiq_processed{label=%q}", labelValue)
metrics.GetOrCreateFloatCounter(metricName).Set(value)
}

Profiling Go web application built with Gorilla's mux with net/http/pprof

I have a relatively big web application written in Go that uses Gorilla's mux for routing. I recently realised that my web application is quite slow and I would like to profile the web application.
After reading about it, it seems that net/http/pprof is what I need. But I can't make it run with mux; even in the case of the most trivial web application.
Does anyone knows how to make that work?
Here is an example of a trivial code that does not work (i.e. nothing is served at /debug).
package main
import (
"fmt"
"github.com/gorilla/mux"
"math"
"net/http"
)
import _ "net/http/pprof"
func SayHello(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 1000000; i++ {
math.Pow(36, 89)
}
fmt.Fprint(w, "Hello!")
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/hello", SayHello)
http.ListenAndServe(":6060", r)
}
My preferred method for this is to just let net/http/pprof register itself to http.DefaultServeMux, and then pass all requests starting with /debug/pprof/ along:
package main
import (
"net/http"
_ "net/http/pprof"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
if err := http.ListenAndServe(":6060", router); err != nil {
panic(err)
}
}
I find that this approach is a lot more stable than one that depends on the implementation of a hidden initialization method, and also guarantees that you didn't miss anything.
user983716 - Thanks for your question and solution!
I was not able to use the links from the web index (http://[my-server]/debug/pprof), until I added a few lines to your solution, like so:
...
func AttachProfiler(router *mux.Router) {
router.HandleFunc("/debug/pprof/", pprof.Index)
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
// Manually add support for paths linked to by index page at /debug/pprof/
router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
router.Handle("/debug/pprof/heap", pprof.Handler("heap"))
router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
router.Handle("/debug/pprof/block", pprof.Handler("block"))
}
...
If anyone has the same problem, I hope this helps!
Sorry for that question. The answer is in the init() function of pprof. One just need to add 4 functions from pprof to the mux router. Here is the fixed code from above.
package main
import (
"fmt"
"github.com/gorilla/mux"
"math"
"net/http"
)
import "net/http/pprof"
func AttachProfiler(router *mux.Router) {
router.HandleFunc("/debug/pprof/", pprof.Index)
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
}
func SayHello(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 1000000; i++ {
math.Pow(36, 89)
}
fmt.Fprint(w, "Hello!")
}
func main() {
r := mux.NewRouter()
AttachProfiler(r)
r.HandleFunc("/hello", SayHello)
http.ListenAndServe(":6060", r)
}
Previous examples not really work on my side.
To use pprof in an existing golang project with gorrila/mux, try to add :
...previous code
func main() {
r := mux.NewRouter()
r.HandleFunc("/hello", SayHello)
go func() {
log.Fatal(http.ListenAndServe(":6061", http.DefaultServeMux))
}()
http.ListenAndServe(":6060", r)
}
then go to http://localhost:6061/debug/pprof/
I did something else, I added another native http server on a different port and it just works out of the box
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
log.Println(http.ListenAndServe(":6060", nil))
}()
log.Fatalln(http.ListenAndServe(":8080", route.Handlers()))
}
Now the pprof endpoint is at :
http://localhost:6060/debug/pprof/ and the applcation is running on port :8080
Just so:
r := mux.NewRouter()
r.PathPrefix("/debug").Handler(http.DefaultServeMux)
Im using https://github.com/julienschmidt/httprouter but i just got this answer from google search.
That's what i did
router := httprouter.New()
router.Handler("GET", "/debug/pprof/profile", http.DefaultServeMux)
router.Handler("GET", "/debug/pprof/heap", http.DefaultServeMux)
I only need this two routes.
This answer is combine of #damien and #user983716 answers.
The following should work:
import (
"net/http"
_ "net/http/pprof"
)
myrouter.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)

Resources