azmr
azmr

Reputation: 311

Working out the number of times a (request handler) function has been called in Go

Context

I'm making a web app that serves dynamically generated pdfs. These contain content from the internet, so every time it serves a pdf, it downloads a number of files to a new temporary folder.

The Problem

I end up with a large number of folders after I load the page once, so it seems that, for some reason, the handler is being called multiple times, which is an issue because I'm downloading multiple times more than I need to of not insubstantial files. I'd like to check at what stage of the process multiple requests are occurring.

The Question

Is there a way of working out how many times a function has been called, quite possibly using closures? (I haven't quite got closures into my mental model for programming yet; I don't completely understand them/how they're used). This would preferably be something involving an int in the language rather than printing something at every stage and counting by hand - I'm looking for a more scalable solution than that (for later situations as well as this one).

Thanks!

Upvotes: 3

Views: 2496

Answers (2)

Kyle Lemons
Kyle Lemons

Reputation: 4756

Counting Calls

To answer the specific question you asked, here is one quick way to count handler executions:

func countCalls(h http.HandlerFunc) http.HandlerFunc {
    var lock sync.Mutex
    var count int
    return func(w http.ResponseWriter, r *http.Request) {
        lock.Lock()
        count++
        w.Header().Set("X-Call-Count", fmt.Sprintf("%d", count))
        lock.Unlock()

        h.ServeHTTP(w, r)
    }
}

http.Handle("/foobar", countCalls(foobarHandler))

This will add a header that you can inspect with your favorite web developer tools; you could also just log it to standard output or something.

Logging Handlers

To expand upon the answers mentioned above, what you probably want to do to debug this and have in place for future use is to log details of each request.

package main

import (
    "flag"
    "log"
    "net/http"
    "os"

    "github.com/gorilla/handlers"
)

var (
    accessLogFile = flag.String("log", "/var/log/yourapp/access.log", "Access log file")
)

func main() {
    accessLog, err := os.OpenFile(*accessLogFile, os.O_CREATE|os.O_WRITE|os.O_APPEND, 0644)
    if err != nil {
        log.Fatalf("Failed to open access log: %s", err)
    }

    wrap := func(f http.HandlerFunc) http.Handler {
        return handlers.LoggingHandler(accessLog, http.HandlerFunc(foobarHandler))
    }

    http.Handle("/foobar", wrap(foobarHandler))

    ...
}

This uses LoggingHandler (or CombinedLoggingHandler) to write a standard Apache format log message that you can either inspect yourself or analyze with various tools.

An example of a log line would be

127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326

which tells you who made the request, when, what the method and URL was, how your server responded, and how long the response was. From this log, you should be able to see exactly what requests are being made, to determine not only how many times your handlers are being called, but exactly what is generating the requests and whether they're to another endpoint (like /favicon.ico).

Upvotes: 1

ANisus
ANisus

Reputation: 77955

Here are two ways you can count function calls, and one for method calls. There are plenty of other ways too, but just to get you started:

Using closure: (not what I would recommended)

package main

import(
    "fmt"
    "sync/atomic"
)

var Foo = func() (func() uint64) {
    var called uint64
    return func() uint64 {
        atomic.AddUint64(&called, 1)
        fmt.Println("Foo!")
        return called
    }
}()

func main() {
    Foo()
    c := Foo()
    fmt.Printf("Foo() is called %d times\n", c)
}

Playground: http://play.golang.org/p/euKbamdI7h

Using global counter:

package main

import (
    "fmt"
    "sync/atomic"
)

var called uint64

func Foo() {
    atomic.AddUint64(&called, 1)
    fmt.Println("Foo!");
}

func main() {
    Foo()
    Foo()
    fmt.Printf("Foo() is called %d times\n", called)
}

Playground: http://play.golang.org/p/3Ib29VCnoF

Counting method calls:

package main

import (
    "fmt"
    "sync/atomic"
)

type T struct {
    Called uint64
}

func (t *T) Foo() {
    atomic.AddUint64(&t.Called, 1)
    fmt.Println("Foo!")
}

func main() {
    var obj T
    obj.Foo()
    obj.Foo()
    fmt.Printf("obj.Foo() is called %d times\n", obj.Called)
}

Playground: http://play.golang.org/p/59eOQdUQU1

Edit:

I just realized that the handler might not be in your own package. In such a case, you might want to write a wrapper:

var called uint64

func Foo() {
    atomic.AddUint64(&called, 1)
    importedPackage.Foo()
}

Edit 2:

Updated the examples to use atomic +1 operations.

Upvotes: 2

Related Questions