Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profiler #6

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: go

go:
- 1.3
- 1.4
- 1.5
- tip
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015 Fog Creek Software
Copyright (c) 2015 Blake Caldwell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
83 changes: 57 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,83 @@
Web-Based Memory Profiler for Go Services
Web-Based Memory Profiler for Go Services [![Build Status](https://travis-ci.org/wblakecaldwell/profiler.svg?branch=master)](https://travis-ci.org/wblakecaldwell/profiler) [![GoDoc](https://godoc.org/github.com/wblakecaldwell/profiler?status.svg)](https://godoc.org/github.com/wblakecaldwell/profiler)
=========================================

This package helps you track your service's memory usage and report custom properties.
Profiler helps you track your service's memory usage and custom key/value diagnostic info.

*TODO: more info, screenshots, examples*
![Profiler Screenshot](screenshot.png)


Enabling Memory Profiling
-------------------------

To enable memory profiling, modify your main method like this:

import (
"net/http"
"github.com/fogcreek/profiler"
)
func main() {
// add handlers to help us track memory usage - they don't track memory until they're told to
profiler.AddMemoryProfilingHandlers()

// listen on port 6060 (pick a port)
http.ListenAndServe(":6060", nil)
}

The simplest way to use the profiler is to add its endpoints to your HTTP listener.
See the [extra_service_info](examples/extra_service_info/) example for how to
serve the profiler's endpoints on its own IP/port.

```go
import (
"net/http"
"github.com/wblakecaldwell/profiler"
)
func main() {
// add the profiler handler endpoints
profiler.AddMemoryProfilingHandlers()

// add realtime extra key/value diagnostic info (optional)
profiler.RegisterExtraServiceInfoRetriever(extraServiceInfo)

// start the profiler on service start (optional)
profiler.StartProfiling()

// listen on port 6060 (pick a port)
http.ListenAndServe(":6060", nil)
}

// extraServiceInfo returns key/value diagnostic info
func extraServiceInfo() map[string]interface{} {
extraInfo := make(map[string]interface{})
extraInfo["uptime"] = fetchUptime()
extraInfo["successful connection count"] = fetchSuccessfulConnectionCount()
extraInfo["failure connection count"] = fetchFailureConnectionCount()
return extraInfo
}
```

Using Memory Profiling
----------------------

Enabling Memory Profiling exposes the following endpoints:

- http://localhost:6060/profiler/memstats: Main page you should visit
- http://localhost:6060/profiler/stop : Stop recording memory statistics
- http://localhost:6060/profiler/start : Start recording memory statistics
- http://localhost:6060/profiler/info.html : Main page you should visit
- http://localhost:6060/profiler/info : JSON data that feeds profiler/info.html


Examples
--------

- http://localhost:6060/profiler/stop: Stop recording memory statistics
View and/or run the three working examples in the [examples](examples/) folder:

- http://localhost:6060/profiler/start: Start recording memory statistics
1. [Simple](examples/simple/): Serving the profiler's endpoints to a service's single HTTP IP:port
2. [Separate Port](examples/separate_port/): Serving the profiler's endpoints on its own IP:port
3. [Extra Service Info](examples/extra_service_info/): Same as the previous example, with the profiler also reporting diagnostic key/value pairs


Working With the Template Files
-------------------------------

We bundle the template files in the Go binary with the 'go-bindata' tool. Everything in
github.com/fogcreek/profiler/profiler-web is bundled up into github.com/fogcreek/profiler/profiler-web.go
Template files are bundled in the Go binary with the 'go-bindata' tool. Everything in
github.com/wblakecaldwell/profiler/profiler-web is bundled up into github.com/wblakecaldwell/profiler/profiler-web.go
with the command, assuming your repository is in $GOPATH/src.

Production Code Generation (Check this in):

go get github.com/jteeuwen/go-bindata/...
go install github.com/jteeuwen/go-bindata/go-bindata
```shell
go get github.com/jteeuwen/go-bindata/...
go install github.com/jteeuwen/go-bindata/go-bindata

go-bindata -prefix "$GOPATH/src/github.com/fogcreek/profiler/profiler-web/" -pkg "profiler" -nocompress -o "$GOPATH/src/github.com/fogcreek/profiler/profiler-web.go" "$GOPATH/src/github.com/fogcreek/profiler/profiler-web"
go-bindata -prefix "$GOPATH/src/github.com/wblakecaldwell/profiler/profiler-web/" -pkg "profiler" -nocompress -o "$GOPATH/src/github.com/wblakecaldwell/profiler/profiler-web.go" "$GOPATH/src/github.com/wblakecaldwell/profiler/profiler-web"
```

If you'd like to make changes to the templates, then use 'go-bindata' in debug mode. Instead of compiling
the contents of the template files into profiler-web.go, it generates code to read the content of the template
Expand All @@ -57,6 +86,8 @@ refresh the browser to see them:

Development Code Generation:

go-bindata -debug -prefix "$GOPATH/src/github.com/fogcreek/profiler/profiler-web/" -pkg "profiler" -nocompress -o "$GOPATH/src/github.com/fogcreek/profiler/profiler-web.go" "$GOPATH/src/github.com/fogcreek/profiler/profiler-web"
```shell
go-bindata -debug -prefix "$GOPATH/src/github.com/wblakecaldwell/profiler/profiler-web/" -pkg "profiler" -nocompress -o "$GOPATH/src/github.com/wblakecaldwell/profiler/profiler-web.go" "$GOPATH/src/github.com/wblakecaldwell/profiler/profiler-web"
```

When you've wrapped up development, make sure to rebuild profiler-web.go to contain the contents of the file with the first non-debug command.
56 changes: 0 additions & 56 deletions doc.go
Original file line number Diff line number Diff line change
@@ -1,60 +1,4 @@
/*
Package profiler contains tools to help profile a running Go service.

Enabling Memory Profiling
-------------------------

To enable memory profiling, modify your main method like this:

import (
"net/http"
"github.com/fogcreek/profiler"
)
func main() {
// listen on port 6060 (pick a port)
http.ListenAndServe(6060, nil)

// add handlers to help us track memory usage - they don't track memory until they're told to
profiler.AddMemoryProfilingHandlers()
}


Using Memory Profiling
----------------------

Enabling Memory Profiling exposes the following endpoints:

- http://localhost:6060/profiler/memstats: Main page you should visit

- http://localhost:6060/profiler/stop: Stop recording memory statistics

- http://localhost:6060/profiler/start: Start recording memory statistics


Working With the Template Files
-------------------------------

We bundle the template files in the Go binary with the 'go-bindata' tool. Everything in
github.com/fogcreek/profiler/profiler-web is bundled up into github.com/fogcreek/profiler/profiler-web.go
with the command, assuming your repository is in $GOPATH/src.

Production Code Generation (Check this in):

go get github.com/jteeuwen/go-bindata/...
go install github.com/jteeuwen/go-bindata/go-bindata

go-bindata -prefix "$GOPATH/src/github.com/fogcreek/profiler/profiler-web/" -pkg "profiler" -nocompress -o "$GOPATH/src/github.com/fogcreek/profiler/profiler-web.go" "$GOPATH/src/github.com/fogcreek/profiler/profiler-web"

If you'd like to make changes to the templates, then use 'go-bindata' in debug mode. Instead of compiling
the contents of the template files into profiler-web.go, it generates code to read the content of the template
files as they exist at that moment. This way, you can start your service, view the page, make changes, then
refresh the browser to see them:

Development Code Generation:

go-bindata -debug -prefix "$GOPATH/src/github.com/fogcreek/profiler/profiler-web/" -pkg "profiler" -nocompress -o "$GOPATH/src/github.com/fogcreek/profiler/profiler-web.go" "$GOPATH/src/github.com/fogcreek/profiler/profiler-web"

When you've wrapped up development, make sure to rebuild profiler-web.go to contain the contents of the file with the first non-debug command.

*/
package profiler
8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Examples
========

View and/or run the three working examples:

1. [Simple](simple/): Serving the profiler's endpoints on the same IP:port as the web service
2. [Separate Port](separate_port/): Serving the profiler's endpoints on its own IP:port
3. [Extra Service Info](extra_service_info/): Same as the previous example, with the profiler also reporting diagnostic key/value pairs
45 changes: 45 additions & 0 deletions examples/extra_service_info/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Example: Sharing Extra Service Information
==========================================

The profiler not only shows you the heap memory usage, but it also gives
your service the opportunity to share some extra diagnostic information as
key/value pairs. You supply the profiler with a function that returns a `map`,
and the profiler will call it when someone is viewing its web page.

```go
// extraServiceInfo implements the profiler.ExtraServiceInfoRetriever interface,
// returning a map of key/value pairs of diagnostic information.
func extraServiceInfo() map[string]interface{} {
extraInfo := make(map[string]interface{})
extraInfo["hello"] = "world"
extraInfo["good number"] = 42
return extraInfo
}

func main() {
// set up the profiler
// ...

// give the profiler a function that returns key/value pairs of extra service info
profiler.RegisterExtraServiceInfoRetriever(extraServiceInfo)
}
```


Run the Example
---------------

Fetch, build, and run the example service:

```shell
go get github.com/wblakecaldwell/profiler
go build github.com/wblakecaldwell/profiler/examples/extra_service_info
./extra_service_info
```

Verify the 'Hello, World!' endpoint at http://localhost:8080

Verify the profiler is running on its own port at http://localhost:6060/profiler/info.html

![Screenshot](screenshot.png)

80 changes: 80 additions & 0 deletions examples/extra_service_info/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Example service with the profiler listening on a separate port,
// and sharing extra diagnostic info as key/value pairs
package main

import (
"fmt"
"github.com/wblakecaldwell/profiler"
"log"
"net/http"
"sync/atomic"
"time"
)

// variables for extra service info
var (
// startTime is the time that the service started
startTime time.Time

// infoHtmlHitCount keeps track of how many times /profiler/info.html is hit
infoHTMLHitCount uint64

// infoHitCount keeps track of how many times the /profiler/info is hit
infoHitCount uint64
)

func init() {
startTime = time.Now().Round(time.Second)
}

func main() {
// start the profiler on its own port
setupProfiler(":6060")

// provide the profiler with a function where it can request key/value pairs as extra service info
profiler.RegisterExtraServiceInfoRetriever(extraServiceInfo)

// Serve your public HTTP endpoints on port 8080
http.HandleFunc("/", helloHandler)
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

// simple handler that just says Hello
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}

// Set up the profiler to listen on the input port:IP string
func setupProfiler(listen string) {
mux := http.NewServeMux()

// wrap /profiler/info.html for hit tracking
mux.HandleFunc("/profiler/info.html", func(w http.ResponseWriter, r *http.Request) {
atomic.AddUint64(&infoHTMLHitCount, 1)
profiler.MemStatsHTMLHandler(w, r)
})

// wrap /profiler/info for hit tracking
mux.HandleFunc("/profiler/info", func(w http.ResponseWriter, r *http.Request) {
atomic.AddUint64(&infoHitCount, 1)
profiler.ProfilingInfoJSONHandler(w, r)
})

mux.HandleFunc("/profiler/start", profiler.StartProfilingHandler)
mux.HandleFunc("/profiler/stop", profiler.StopProfilingHandler)
log.Printf("Starting profiler on %s\n", listen)
go func() {
log.Fatal(http.ListenAndServe(listen, mux))
}()
}

// extraServiceInfo implements the profiler.ExtraServiceInfoRetriever interface,
// returning a map of key/value pairs of diagnostic information.
func extraServiceInfo() map[string]interface{} {
extraInfo := make(map[string]interface{})
extraInfo["uptime"] = time.Now().Round(time.Second).Sub(startTime).String()
extraInfo["hit count: /profiler/info.html"] = atomic.LoadUint64(&infoHTMLHitCount)
extraInfo["hit count: /profiler/info"] = atomic.LoadUint64(&infoHitCount)
return extraInfo
}
Binary file added examples/extra_service_info/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions examples/separate_port/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Example: Profiler on a Separate Port
====================================

If you're adding the profiler to a service that serves HTTP endpoints, you might
want the profiler listening on its own IP/port. This makes it easier to keep
your profiling information private, and set up firewall rules to not serve this
IP/port publicly.

The trick here is to not use the DefaultServeMux. This is all you need to add to
your project to set up the profiler to listen on its own port:

```go
// Set up the profiler to listen on the input port:IP string
func setupProfiler(listen string) {
mux := http.NewServeMux()
mux.HandleFunc("/profiler/info.html", profiler.MemStatsHTMLHandler)
mux.HandleFunc("/profiler/info", profiler.ProfilingInfoJSONHandler)
mux.HandleFunc("/profiler/start", profiler.StartProfilingHandler)
mux.HandleFunc("/profiler/stop", profiler.StopProfilingHandler)
fmt.Printf("Starting profiler on %s\n", listen)
go http.ListenAndServe(listen, mux)
}
```


Run the Example
---------------

Fetch, build, and run the example service:

```shell
go get github.com/wblakecaldwell/profiler
go build github.com/wblakecaldwell/profiler/examples/separate_port
./separate_port
```

Verify the 'Hello, World!' endpoint at http://localhost:8080

Verify the profiler is running on its own port at http://localhost:6060/profiler/info.html

![Screenshot](screenshot.png)
Loading