├── LICENSE ├── README.md ├── examples ├── closure │ └── main.go ├── graphite-reporting │ └── main.go └── middleware │ └── main.go ├── stopwatch.go └── stopwatch_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Didip Kerabat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/didip/stopwatch?status.svg)](http://godoc.org/github.com/didip/stopwatch) 2 | [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/didip/stopwatch/master/LICENSE) 3 | 4 | ## Stopwatch 5 | 6 | A small library to measure latency of things. 7 | 8 | It can measure: 9 | 10 | 1. Arbitrary closure's latency. 11 | 12 | 2. Request latency via middleware pattern. 13 | 14 | 15 | ## Five Minutes Tutorial 16 | 17 | #### 1. Closure 18 | ```go 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "github.com/didip/stopwatch" 24 | ) 25 | 26 | func main() { 27 | a := 1 28 | f := func() { 29 | for i := 1; i <= 10; i++ { 30 | a = a + 1 31 | } 32 | } 33 | 34 | latency := stopwatch.Measure(f) 35 | 36 | fmt.Printf("Latency in nanoseconds: %v, Result: %v\n", latency, a) 37 | } 38 | ``` 39 | 40 | #### 2. Middleware 41 | ```go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | "github.com/didip/stopwatch" 47 | "net/http" 48 | ) 49 | 50 | func HelloHandler(w http.ResponseWriter, req *http.Request) { 51 | w.Write([]byte("Hello, World!")) 52 | } 53 | 54 | func main() { 55 | // 1. Create a channel to receive latency result 56 | helloHandlerLatencyChan := make(chan int64) 57 | 58 | // 2. Pull latency result asynchronously. 59 | go func() { 60 | for { 61 | select { 62 | case latency := <-helloHandlerLatencyChan: 63 | fmt.Printf("Latency of HelloHandler in nanoseconds: %v\n", latency) 64 | } 65 | } 66 | }() 67 | 68 | fmt.Println("Starting HTTP server on :12345") 69 | http.Handle("/", stopwatch.LatencyFuncHandler(helloHandlerLatencyChan, []string{"GET"}, HelloHandler)) 70 | http.ListenAndServe(":12345", nil) 71 | } 72 | ``` 73 | 74 | 75 | ## My other Go libraries 76 | 77 | * [Tollbooth](https://github.com/didip/tollbooth): Simple middleware to rate-limit HTTP requests. 78 | 79 | * [Gomet](https://github.com/didip/gomet): Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket. -------------------------------------------------------------------------------- /examples/closure/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/didip/stopwatch" 6 | ) 7 | 8 | func main() { 9 | a := 1 10 | f := func() { 11 | for i := 1; i <= 10; i++ { 12 | a = a + 1 13 | } 14 | } 15 | 16 | latency := stopwatch.Measure(f) 17 | 18 | fmt.Printf("Latency in nanoseconds: %v, Result: %v\n", latency, a) 19 | } 20 | -------------------------------------------------------------------------------- /examples/graphite-reporting/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/didip/stopwatch" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func HelloHandler(w http.ResponseWriter, req *http.Request) { 16 | w.Write([]byte("Hello, World!")) 17 | } 18 | 19 | func main() { 20 | graphiteAddrString := "localhost:2003" 21 | hostname, _ := os.Hostname() 22 | hostnameUnderscore := strings.Replace(hostname, ".", "_", -1) 23 | hostnameUnderscore = strings.Replace(hostnameUnderscore, "-", "_", -1) 24 | 25 | // 1. Create TCP connection 26 | graphiteAddr, err := net.ResolveTCPAddr("tcp", graphiteAddrString) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | conn, err := net.DialTCP("tcp", nil, graphiteAddr) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | defer conn.Close() 36 | 37 | writer := bufio.NewWriter(conn) 38 | 39 | // 2. Create a channel to receive latency result 40 | helloHandlerLatencyChan := make(chan int64) 41 | 42 | // 3. Pull latency result asynchronously. 43 | go func() { 44 | for { 45 | for latency := range helloHandlerLatencyChan { 46 | payload := fmt.Sprintf("graphite-reporting.%s.requests.HelloHandler %d %d\n", hostnameUnderscore, latency, time.Now().Unix()) 47 | fmt.Printf("Payload for graphite: %v", payload) 48 | 49 | fmt.Fprintf(writer, payload) 50 | writer.Flush() 51 | } 52 | } 53 | }() 54 | 55 | fmt.Println("Starting HTTP server on :12345") 56 | http.Handle("/", stopwatch.LatencyFuncHandler(helloHandlerLatencyChan, []string{"GET"}, HelloHandler)) 57 | http.ListenAndServe(":12345", nil) 58 | } 59 | -------------------------------------------------------------------------------- /examples/middleware/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/didip/stopwatch" 6 | "net/http" 7 | ) 8 | 9 | func HelloHandler(w http.ResponseWriter, req *http.Request) { 10 | w.Write([]byte("Hello, World!")) 11 | } 12 | 13 | func main() { 14 | // 1. Create a channel to receive latency result 15 | helloHandlerLatencyChan := make(chan int64) 16 | 17 | // 2. Pull latency result asynchronously. 18 | go func() { 19 | for { 20 | select { 21 | case latency := <-helloHandlerLatencyChan: 22 | fmt.Printf("Latency of HelloHandler in nanoseconds: %v\n", latency) 23 | } 24 | } 25 | }() 26 | 27 | fmt.Println("Starting HTTP server on :12345") 28 | http.Handle("/", stopwatch.LatencyFuncHandler(helloHandlerLatencyChan, []string{"GET"}, HelloHandler)) 29 | http.ListenAndServe(":12345", nil) 30 | } 31 | -------------------------------------------------------------------------------- /stopwatch.go: -------------------------------------------------------------------------------- 1 | package stopwatch 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // Measure latency in nanoseconds 9 | func Measure(f func()) int64 { 10 | start := time.Now().UnixNano() 11 | f() 12 | return time.Now().UnixNano() - start 13 | } 14 | 15 | // LatencyHandler is a middleware that measures latency given http.Handler struct. 16 | func LatencyHandler(resultChan chan int64, methods []string, next http.Handler) http.Handler { 17 | middle := func(w http.ResponseWriter, r *http.Request) { 18 | foundMethod := false 19 | for _, method := range methods { 20 | if r.Method == method { 21 | foundMethod = true 22 | break 23 | } 24 | } 25 | 26 | if foundMethod && resultChan != nil { 27 | start := time.Now().UnixNano() 28 | defer func() { 29 | resultChan <- (time.Now().UnixNano() - start) 30 | }() 31 | } 32 | 33 | next.ServeHTTP(w, r) 34 | } 35 | 36 | return http.HandlerFunc(middle) 37 | } 38 | 39 | // LatencyFuncHandler is a middleware that measures latency given request handler function. 40 | func LatencyFuncHandler(resultChan chan int64, methods []string, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler { 41 | return LatencyHandler(resultChan, methods, http.HandlerFunc(nextFunc)) 42 | } 43 | -------------------------------------------------------------------------------- /stopwatch_test.go: -------------------------------------------------------------------------------- 1 | package stopwatch 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSimpleWithClosureScope(t *testing.T) { 8 | a := 1 9 | f := func() { 10 | for i := 1; i <= 10; i++ { 11 | a = a + 1 12 | } 13 | } 14 | 15 | latency := Measure(f) 16 | 17 | if latency < 0 { 18 | t.Errorf("Fail to measure latency of a simple function. Latency: %v, Result: %v", latency, a) 19 | } 20 | } 21 | --------------------------------------------------------------------------------