aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/heroku/x
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/heroku/x')
-rw-r--r--vendor/github.com/heroku/x/hmetrics/doc.go5
-rw-r--r--vendor/github.com/heroku/x/hmetrics/hmetrics.go169
-rw-r--r--vendor/github.com/heroku/x/hmetrics/onload/README.md7
-rw-r--r--vendor/github.com/heroku/x/hmetrics/onload/init.go25
4 files changed, 206 insertions, 0 deletions
diff --git a/vendor/github.com/heroku/x/hmetrics/doc.go b/vendor/github.com/heroku/x/hmetrics/doc.go
new file mode 100644
index 0000000..f33757c
--- /dev/null
+++ b/vendor/github.com/heroku/x/hmetrics/doc.go
@@ -0,0 +1,5 @@
+/*
+package hmetrics is a self-contained client for heroku Go runtime metrics.
+*/
+
+package hmetrics
diff --git a/vendor/github.com/heroku/x/hmetrics/hmetrics.go b/vendor/github.com/heroku/x/hmetrics/hmetrics.go
new file mode 100644
index 0000000..4ae88f6
--- /dev/null
+++ b/vendor/github.com/heroku/x/hmetrics/hmetrics.go
@@ -0,0 +1,169 @@
+package hmetrics
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "runtime"
+ "sync"
+ "time"
+)
+
+const (
+ metricWaitTime = 20 * time.Second
+)
+
+var (
+ DefaultEndpoint string
+)
+
+func init() {
+ DefaultEndpoint = os.Getenv("HEROKU_METRICS_URL")
+}
+
+type AlreadyStarted struct{}
+
+func (as AlreadyStarted) Error() string {
+ return "already started"
+}
+
+func (as AlreadyStarted) Fatal() bool {
+ return false
+}
+
+type HerokuMetricsURLUnset struct{}
+
+func (e HerokuMetricsURLUnset) Error() string {
+ return "cannot report metrics because HEROKU_METRICS_URL is unset"
+}
+
+func (e HerokuMetricsURLUnset) Fatal() bool {
+ return true
+}
+
+var (
+ mu sync.Mutex
+ started bool
+)
+
+// ErrHandler receives any errors encountered during collection or reporting of metrics to Heroku. Processing of metrics
+// continues if the ErrHandler returns nil, but aborts if the ErrHandler itself returns an error.
+type ErrHandler func(err error) error
+
+func Report(ctx context.Context, ef ErrHandler) error {
+ mu.Lock()
+ defer mu.Unlock()
+ if started {
+ return AlreadyStarted{}
+ }
+ endpoint := DefaultEndpoint
+ if endpoint == "" {
+ return HerokuMetricsURLUnset{}
+ }
+ if ef == nil {
+ ef = func(_ error) error { return nil }
+ }
+ go report(ctx, endpoint, ef)
+ started = true
+ return nil
+}
+
+// The only thing that should come after an exit() is a return.
+// Best to use in a function that can defer it.
+func exit() {
+ mu.Lock()
+ defer mu.Unlock()
+ started = false
+}
+
+func report(ctx context.Context, endpoint string, ef ErrHandler) {
+ defer exit()
+
+ t := time.NewTicker(metricWaitTime)
+ defer t.Stop()
+
+ for {
+ select {
+ case <-t.C:
+ case <-ctx.Done():
+ return
+ }
+
+ if err := gatherMetrics(); err != nil {
+ if err := ef(err); err != nil {
+ return
+ }
+ continue
+ }
+ if err := submitPayload(ctx, endpoint); err != nil {
+ if err := ef(err); err != nil {
+ return
+ }
+ continue
+ }
+ }
+}
+
+var (
+ lastGCPause uint64
+ lastNumGC uint32
+ buf bytes.Buffer
+)
+
+// TODO: If we ever have high frequency charts HeapIdle minus HeapReleased could be interesting.
+func gatherMetrics() error {
+ var stats runtime.MemStats
+ runtime.ReadMemStats(&stats)
+
+ // cribbed from https://github.com/codahale/metrics/blob/master/runtime/memstats.go
+
+ pauseNS := stats.PauseTotalNs - lastGCPause
+ lastGCPause = stats.PauseTotalNs
+
+ numGC := stats.NumGC - lastNumGC
+ lastNumGC = stats.NumGC
+
+ result := struct {
+ Counters map[string]float64 `json:"counters"`
+ Gauges map[string]float64 `json:"gauges"`
+ }{
+ Counters: map[string]float64{
+ "go.gc.collections": float64(numGC),
+ "go.gc.pause.ns": float64(pauseNS),
+ },
+ Gauges: map[string]float64{
+ "go.memory.heap.bytes": float64(stats.Alloc),
+ "go.memory.stack.bytes": float64(stats.StackInuse),
+ "go.memory.heap.objects": float64(stats.Mallocs - stats.Frees), // Number of "live" objects.
+ "go.gc.goal": float64(stats.NextGC), // Goal heap size for next GC.
+ "go.routines": float64(runtime.NumGoroutine()), // Current number of goroutines.
+ },
+ }
+
+ buf.Reset()
+ return json.NewEncoder(&buf).Encode(result)
+}
+
+func submitPayload(ctx context.Context, where string) error {
+ req, err := http.NewRequest("POST", where, &buf)
+ if err != nil {
+ return err
+ }
+ req = req.WithContext(ctx)
+ req.Header.Add("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("expected %v (http.StatusOK) but got %s", http.StatusOK, resp.Status)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/heroku/x/hmetrics/onload/README.md b/vendor/github.com/heroku/x/hmetrics/onload/README.md
new file mode 100644
index 0000000..037068e
--- /dev/null
+++ b/vendor/github.com/heroku/x/hmetrics/onload/README.md
@@ -0,0 +1,7 @@
+runtime-metrics
+===============
+
+This will only be useful if you are inside the runtime metrics private beta.
+
+This package, once loaded, will implicitly dump a subset of runtime performace
+metrics to the URL denoted by `HEROKU_METRICS_URL`
diff --git a/vendor/github.com/heroku/x/hmetrics/onload/init.go b/vendor/github.com/heroku/x/hmetrics/onload/init.go
new file mode 100644
index 0000000..d6fb49d
--- /dev/null
+++ b/vendor/github.com/heroku/x/hmetrics/onload/init.go
@@ -0,0 +1,25 @@
+/*
+package onload automatically starts up the hmetrics reporting.
+
+Use this package when you don't care about shutting down them metrics reporting or being notified of any reporting
+errors.
+
+usage:
+
+import (
+ _ "github.com/heroku/x/hmetrics/onload"
+)
+
+*/
+
+package onloads
+
+import (
+ "context"
+
+ "github.com/heroku/x/hmetrics"
+)
+
+func init() {
+ hmetrics.Report(context.Background(), nil)
+}