{"action":"created","comment":{"url":"https://api.github.com/repos/go-kit/kit/pulls/comments/63260213","id":63260213,"diff_hunk":"@@ -0,0 +1,335 @@\n+// Package graphite implements a Graphite backend for package metrics. Metrics\n+// will be emitted to a Graphite server in the plaintext protocol\n+// (http://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol)\n+// which looks like:\n+//   \"<metric path> <metric value> <metric timestamp>\"\n+//\n+// The current implementation ignores fields.\n+package graphite\n+\n+import (\n+\t\"bufio\"\n+\t\"fmt\"\n+\t\"io\"\n+\t\"math\"\n+\t\"net\"\n+\t\"sort\"\n+\t\"sync\"\n+\t\"sync/atomic\"\n+\t\"time\"\n+\n+\t\"github.com/codahale/hdrhistogram\"\n+\t\"github.com/go-kit/kit/log\"\n+\t\"github.com/go-kit/kit/metrics\"\n+)\n+\n+// Emitter will keep track of all metrics and, once started,\n+// will emit the metrics via the Flush method to the given address.\n+type Emitter struct {\n+\tprefix string\n+\n+\taddr  string\n+\ttcp   bool\n+\tconn  net.Conn\n+\tstart sync.Once\n+\tstop  chan bool\n+\n+\tmtx        sync.Mutex\n+\tcounters   []*counter\n+\thistograms []*windowedHistogram\n+\tgauges     []*gauge\n+\n+\tlogger log.Logger\n+}\n+\n+// NewEmitter will return an Emitter that will prefix all\n+// metrics names with the given prefix. Once started, it will attempt to create\n+// a TCP or a UDP connection with the given address and periodically post\n+// metrics to the connection in the Graphite plaintext protocol.\n+// If the provided `tcp` parameter is false, a UDP connection will be used.\n+func NewEmitter(addr string, tcp bool, metricsPrefix string, logger log.Logger) *Emitter {\n+\treturn &Emitter{\n+\t\taddr:   addr,\n+\t\ttcp:    tcp,\n+\t\tstop:   make(chan bool),\n+\t\tprefix: metricsPrefix,\n+\t\tlogger: logger,\n+\t}\n+}\n+\n+// NewCounter returns a Counter whose value will be periodically emitted in\n+// a Graphite-compatible format once the Emitter is started. Fields are ignored.\n+func (e *Emitter) NewCounter(name string) metrics.Counter {\n+\t// only one flush at a time\n+\tc := &counter{name, 0}\n+\te.mtx.Lock()\n+\te.counters = append(e.counters, c)\n+\te.mtx.Unlock()\n+\treturn c\n+}\n+\n+// NewHistogram is taken from http://github.com/codahale/metrics. It returns a\n+// windowed HDR histogram which drops data older than five minutes.\n+//\n+// The histogram exposes metrics for each passed quantile as gauges. Quantiles\n+// should be integers in the range 1..99. The gauge names are assigned by\n+// using the passed name as a prefix and appending \"_pNN\" e.g. \"_p50\".\n+//\n+// The values of this histogram will be periodically emitted in a Graphite-compatible\n+// format once the Emitter is started. Fields are ignored.\n+func (e *Emitter) NewHistogram(name string, minValue, maxValue int64, sigfigs int, quantiles ...int) (metrics.Histogram, error) {\n+\tgauges := map[int]metrics.Gauge{}\n+\tfor _, quantile := range quantiles {\n+\t\tif quantile <= 0 || quantile >= 100 {\n+\t\t\treturn nil, fmt.Errorf(\"invalid quantile %d\", quantile)\n+\t\t}\n+\t\tgauges[quantile] = e.gauge(fmt.Sprintf(\"%s_p%02d\", name, quantile))\n+\t}\n+\th := newWindowedHistogram(name, minValue, maxValue, sigfigs, gauges, e.logger)\n+\n+\te.mtx.Lock()\n+\te.histograms = append(e.histograms, h)\n+\te.mtx.Unlock()\n+\treturn h, nil\n+}\n+\n+// NewTimeHistogram returns a TimeHistogram wrapper around the windowed\n+// HDR histrogram provided by this package.\n+func (e *Emitter) NewTimeHistogram(name string, unit time.Duration, minValue, maxValue int64, sigfigs int, quantiles ...int) (metrics.TimeHistogram, error) {\n+\th, err := e.NewHistogram(name, minValue, maxValue, sigfigs, quantiles...)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn metrics.NewTimeHistogram(unit, h), nil\n+}\n+\n+// NewGauge returns a Gauge whose value will be periodically emitted in\n+// a Graphite-compatible format once the Emitter is started. Fields are ignored.\n+func (e *Emitter) NewGauge(name string) metrics.Gauge {\n+\te.mtx.Lock()\n+\tdefer e.mtx.Unlock()\n+\treturn e.gauge(name)\n+}\n+\n+func (e *Emitter) gauge(name string) metrics.Gauge {\n+\tg := &gauge{name, 0}\n+\te.gauges = append(e.gauges, g)\n+\treturn g\n+}\n+\n+func (e *Emitter) dial() error {\n+\tif e.tcp {\n+\t\ttAddr, err := net.ResolveTCPAddr(\"tcp\", e.addr)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\te.conn, err = net.DialTCP(\"tcp\", nil, tAddr)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t} else {\n+\t\tuAddr, err := net.ResolveUDPAddr(\"udp\", e.addr)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\te.conn, err = net.DialUDP(\"udp\", nil, uAddr)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+// Start will kick off a background goroutine to\n+// call Flush once every interval.\n+func (e *Emitter) Start(interval time.Duration) error {\n+\tvar err error\n+\te.start.Do(func() {\n+\t\terr = e.dial()\n+\t\tif err != nil {\n+\t\t\treturn\n+\t\t}\n+\t\tgo func() {\n+\t\t\tt := time.Tick(interval)\n+\t\t\tfor {\n+\t\t\t\tselect {\n+\t\t\t\tcase <-t:\n+\t\t\t\t\te.Flush()","path":"metrics/graphite/graphite.go","position":150,"original_position":157,"commit_id":"db17f7448df28586ed04eeda15b61ecb40046241","original_commit_id":"f3a0f4c23fdec0b0547adcd0deff1a4cb0d2287c","user":{"login":"peterbourgon","id":114509,"avatar_url":"https://avatars.githubusercontent.com/u/114509?v=3","gravatar_id":"","url":"https://api.github.com/users/peterbourgon","html_url":"https://github.com/peterbourgon","followers_url":"https://api.github.com/users/peterbourgon/followers","following_url":"https://api.github.com/users/peterbourgon/following{/other_user}","gists_url":"https://api.github.com/users/peterbourgon/gists{/gist_id}","starred_url":"https://api.github.com/users/peterbourgon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/peterbourgon/subscriptions","organizations_url":"https://api.github.com/users/peterbourgon/orgs","repos_url":"https://api.github.com/users/peterbourgon/repos","events_url":"https://api.github.com/users/peterbourgon/events{/privacy}","received_events_url":"https://api.github.com/users/peterbourgon/received_events","type":"User","site_admin":false},"body":"@jprobinson https://github.com/jprobinson/kit/pull/1 :inception.jpg:","created_at":"2016-05-13T22:33:22Z","updated_at":"2016-05-13T22:33:22Z","html_url":"https://github.com/go-kit/kit/pull/259#discussion_r63260213","pull_request_url":"https://api.github.com/repos/go-kit/kit/pulls/259","_links":{"self":{"href":"https://api.github.com/repos/go-kit/kit/pulls/comments/63260213"},"html":{"href":"https://github.com/go-kit/kit/pull/259#discussion_r63260213"},"pull_request":{"href":"https://api.github.com/repos/go-kit/kit/pulls/259"}}},"pull_request":{"url":"https://api.github.com/repos/go-kit/kit/pulls/259","id":69713913,"html_url":"https://github.com/go-kit/kit/pull/259","diff_url":"https://github.com/go-kit/kit/pull/259.diff","patch_url":"https://github.com/go-kit/kit/pull/259.patch","issue_url":"https://api.github.com/repos/go-kit/kit/issues/259","number":259,"state":"open","locked":false,"title":"Adding package `metrics/graphite`","user":{"login":"jprobinson","id":1386065,"avatar_url":"https://avatars.githubusercontent.com/u/1386065?v=3","gravatar_id":"","url":"https://api.github.com/users/jprobinson","html_url":"https://github.com/jprobinson","followers_url":"https://api.github.com/users/jprobinson/followers","following_url":"https://api.github.com/users/jprobinson/following{/other_user}","gists_url":"https://api.github.com/users/jprobinson/gists{/gist_id}","starred_url":"https://api.github.com/users/jprobinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jprobinson/subscriptions","organizations_url":"https://api.github.com/users/jprobinson/orgs","repos_url":"https://api.github.com/users/jprobinson/repos","events_url":"https://api.github.com/users/jprobinson/events{/privacy}","received_events_url":"https://api.github.com/users/jprobinson/received_events","type":"User","site_admin":false},"body":"This is the first whack at resolving issue #251.\r\n\r\nWith this new package, metrics will be managed and emitted to a Graphite host via a the new `Emitter`:\r\n\r\n```\r\ntype Emitter interface {\r\n    NewCounter(string) metrics.Counter\r\n    NewHistogram(string, int64, int64, int, ...int) metrics.Histogram\r\n    NewTimeHistogram(string, time.Duration, int64, int64, int, ...int) metrics.TimeHistogram\r\n    NewGauge(string) metrics.Gauge\r\n\r\n    Start(time.Duration)\r\n    Flush() error\r\n}\r\n```\r\n\r\nThe normal pattern of use should be:\r\n  1. Get a new emitter via `NewEmitter`\r\n  2. Register any metrics with the emitter (`NewCounter`, `NewGauge`, etc.)\r\n  3. Call `Start` on the emitter to kick off a goroutine to periodically emit metrics to Graphite.\r\n  4. Call `Flush` on the emitter at shutdown to flush current metrics ","created_at":"2016-05-11T17:49:46Z","updated_at":"2016-05-13T22:33:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2c47e5e6ad9c130c8d9bc91bcc7d6fa7961e6f01","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/go-kit/kit/pulls/259/commits","review_comments_url":"https://api.github.com/repos/go-kit/kit/pulls/259/comments","review_comment_url":"https://api.github.com/repos/go-kit/kit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/go-kit/kit/issues/259/comments","statuses_url":"https://api.github.com/repos/go-kit/kit/statuses/db17f7448df28586ed04eeda15b61ecb40046241","head":{"label":"jprobinson:master","ref":"master","sha":"db17f7448df28586ed04eeda15b61ecb40046241","user":{"login":"jprobinson","id":1386065,"avatar_url":"https://avatars.githubusercontent.com/u/1386065?v=3","gravatar_id":"","url":"https://api.github.com/users/jprobinson","html_url":"https://github.com/jprobinson","followers_url":"https://api.github.com/users/jprobinson/followers","following_url":"https://api.github.com/users/jprobinson/following{/other_user}","gists_url":"https://api.github.com/users/jprobinson/gists{/gist_id}","starred_url":"https://api.github.com/users/jprobinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jprobinson/subscriptions","organizations_url":"https://api.github.com/users/jprobinson/orgs","repos_url":"https://api.github.com/users/jprobinson/repos","events_url":"https://api.github.com/users/jprobinson/events{/privacy}","received_events_url":"https://api.github.com/users/jprobinson/received_events","type":"User","site_admin":false},"repo":{"id":58155983,"name":"kit","full_name":"jprobinson/kit","owner":{"login":"jprobinson","id":1386065,"avatar_url":"https://avatars.githubusercontent.com/u/1386065?v=3","gravatar_id":"","url":"https://api.github.com/users/jprobinson","html_url":"https://github.com/jprobinson","followers_url":"https://api.github.com/users/jprobinson/followers","following_url":"https://api.github.com/users/jprobinson/following{/other_user}","gists_url":"https://api.github.com/users/jprobinson/gists{/gist_id}","starred_url":"https://api.github.com/users/jprobinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jprobinson/subscriptions","organizations_url":"https://api.github.com/users/jprobinson/orgs","repos_url":"https://api.github.com/users/jprobinson/repos","events_url":"https://api.github.com/users/jprobinson/events{/privacy}","received_events_url":"https://api.github.com/users/jprobinson/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jprobinson/kit","description":"A standard library for microservices.","fork":true,"url":"https://api.github.com/repos/jprobinson/kit","forks_url":"https://api.github.com/repos/jprobinson/kit/forks","keys_url":"https://api.github.com/repos/jprobinson/kit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jprobinson/kit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jprobinson/kit/teams","hooks_url":"https://api.github.com/repos/jprobinson/kit/hooks","issue_events_url":"https://api.github.com/repos/jprobinson/kit/issues/events{/number}","events_url":"https://api.github.com/repos/jprobinson/kit/events","assignees_url":"https://api.github.com/repos/jprobinson/kit/assignees{/user}","branches_url":"https://api.github.com/repos/jprobinson/kit/branches{/branch}","tags_url":"https://api.github.com/repos/jprobinson/kit/tags","blobs_url":"https://api.github.com/repos/jprobinson/kit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jprobinson/kit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jprobinson/kit/git/refs{/sha}","trees_url":"https://api.github.com/repos/jprobinson/kit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jprobinson/kit/statuses/{sha}","languages_url":"https://api.github.com/repos/jprobinson/kit/languages","stargazers_url":"https://api.github.com/repos/jprobinson/kit/stargazers","contributors_url":"https://api.github.com/repos/jprobinson/kit/contributors","subscribers_url":"https://api.github.com/repos/jprobinson/kit/subscribers","subscription_url":"https://api.github.com/repos/jprobinson/kit/subscription","commits_url":"https://api.github.com/repos/jprobinson/kit/commits{/sha}","git_commits_url":"https://api.github.com/repos/jprobinson/kit/git/commits{/sha}","comments_url":"https://api.github.com/repos/jprobinson/kit/comments{/number}","issue_comment_url":"https://api.github.com/repos/jprobinson/kit/issues/comments{/number}","contents_url":"https://api.github.com/repos/jprobinson/kit/contents/{+path}","compare_url":"https://api.github.com/repos/jprobinson/kit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jprobinson/kit/merges","archive_url":"https://api.github.com/repos/jprobinson/kit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jprobinson/kit/downloads","issues_url":"https://api.github.com/repos/jprobinson/kit/issues{/number}","pulls_url":"https://api.github.com/repos/jprobinson/kit/pulls{/number}","milestones_url":"https://api.github.com/repos/jprobinson/kit/milestones{/number}","notifications_url":"https://api.github.com/repos/jprobinson/kit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jprobinson/kit/labels{/name}","releases_url":"https://api.github.com/repos/jprobinson/kit/releases{/id}","deployments_url":"https://api.github.com/repos/jprobinson/kit/deployments","created_at":"2016-05-05T19:39:15Z","updated_at":"2016-05-05T19:39:17Z","pushed_at":"2016-05-13T22:32:58Z","git_url":"git://github.com/jprobinson/kit.git","ssh_url":"git@github.com:jprobinson/kit.git","clone_url":"https://github.com/jprobinson/kit.git","svn_url":"https://github.com/jprobinson/kit","homepage":"http://gokit.io","size":4715,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"go-kit:master","ref":"master","sha":"a86152119bc3b92e1e6f3923f0e098224b7ba2cc","user":{"login":"go-kit","id":10981023,"avatar_url":"https://avatars.githubusercontent.com/u/10981023?v=3","gravatar_id":"","url":"https://api.github.com/users/go-kit","html_url":"https://github.com/go-kit","followers_url":"https://api.github.com/users/go-kit/followers","following_url":"https://api.github.com/users/go-kit/following{/other_user}","gists_url":"https://api.github.com/users/go-kit/gists{/gist_id}","starred_url":"https://api.github.com/users/go-kit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/go-kit/subscriptions","organizations_url":"https://api.github.com/users/go-kit/orgs","repos_url":"https://api.github.com/users/go-kit/repos","events_url":"https://api.github.com/users/go-kit/events{/privacy}","received_events_url":"https://api.github.com/users/go-kit/received_events","type":"Organization","site_admin":false},"repo":{"id":30215630,"name":"kit","full_name":"go-kit/kit","owner":{"login":"go-kit","id":10981023,"avatar_url":"https://avatars.githubusercontent.com/u/10981023?v=3","gravatar_id":"","url":"https://api.github.com/users/go-kit","html_url":"https://github.com/go-kit","followers_url":"https://api.github.com/users/go-kit/followers","following_url":"https://api.github.com/users/go-kit/following{/other_user}","gists_url":"https://api.github.com/users/go-kit/gists{/gist_id}","starred_url":"https://api.github.com/users/go-kit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/go-kit/subscriptions","organizations_url":"https://api.github.com/users/go-kit/orgs","repos_url":"https://api.github.com/users/go-kit/repos","events_url":"https://api.github.com/users/go-kit/events{/privacy}","received_events_url":"https://api.github.com/users/go-kit/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/go-kit/kit","description":"A standard library for microservices.","fork":false,"url":"https://api.github.com/repos/go-kit/kit","forks_url":"https://api.github.com/repos/go-kit/kit/forks","keys_url":"https://api.github.com/repos/go-kit/kit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/go-kit/kit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/go-kit/kit/teams","hooks_url":"https://api.github.com/repos/go-kit/kit/hooks","issue_events_url":"https://api.github.com/repos/go-kit/kit/issues/events{/number}","events_url":"https://api.github.com/repos/go-kit/kit/events","assignees_url":"https://api.github.com/repos/go-kit/kit/assignees{/user}","branches_url":"https://api.github.com/repos/go-kit/kit/branches{/branch}","tags_url":"https://api.github.com/repos/go-kit/kit/tags","blobs_url":"https://api.github.com/repos/go-kit/kit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/go-kit/kit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/go-kit/kit/git/refs{/sha}","trees_url":"https://api.github.com/repos/go-kit/kit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/go-kit/kit/statuses/{sha}","languages_url":"https://api.github.com/repos/go-kit/kit/languages","stargazers_url":"https://api.github.com/repos/go-kit/kit/stargazers","contributors_url":"https://api.github.com/repos/go-kit/kit/contributors","subscribers_url":"https://api.github.com/repos/go-kit/kit/subscribers","subscription_url":"https://api.github.com/repos/go-kit/kit/subscription","commits_url":"https://api.github.com/repos/go-kit/kit/commits{/sha}","git_commits_url":"https://api.github.com/repos/go-kit/kit/git/commits{/sha}","comments_url":"https://api.github.com/repos/go-kit/kit/comments{/number}","issue_comment_url":"https://api.github.com/repos/go-kit/kit/issues/comments{/number}","contents_url":"https://api.github.com/repos/go-kit/kit/contents/{+path}","compare_url":"https://api.github.com/repos/go-kit/kit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/go-kit/kit/merges","archive_url":"https://api.github.com/repos/go-kit/kit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/go-kit/kit/downloads","issues_url":"https://api.github.com/repos/go-kit/kit/issues{/number}","pulls_url":"https://api.github.com/repos/go-kit/kit/pulls{/number}","milestones_url":"https://api.github.com/repos/go-kit/kit/milestones{/number}","notifications_url":"https://api.github.com/repos/go-kit/kit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/go-kit/kit/labels{/name}","releases_url":"https://api.github.com/repos/go-kit/kit/releases{/id}","deployments_url":"https://api.github.com/repos/go-kit/kit/deployments","created_at":"2015-02-03T00:01:19Z","updated_at":"2016-05-13T21:43:23Z","pushed_at":"2016-05-13T22:25:53Z","git_url":"git://github.com/go-kit/kit.git","ssh_url":"git@github.com:go-kit/kit.git","clone_url":"https://github.com/go-kit/kit.git","svn_url":"https://github.com/go-kit/kit","homepage":"http://gokit.io","size":7896,"stargazers_count":4257,"watchers_count":4257,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":327,"mirror_url":null,"open_issues_count":25,"forks":327,"open_issues":25,"watchers":4257,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/go-kit/kit/pulls/259"},"html":{"href":"https://github.com/go-kit/kit/pull/259"},"issue":{"href":"https://api.github.com/repos/go-kit/kit/issues/259"},"comments":{"href":"https://api.github.com/repos/go-kit/kit/issues/259/comments"},"review_comments":{"href":"https://api.github.com/repos/go-kit/kit/pulls/259/comments"},"review_comment":{"href":"https://api.github.com/repos/go-kit/kit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/go-kit/kit/pulls/259/commits"},"statuses":{"href":"https://api.github.com/repos/go-kit/kit/statuses/db17f7448df28586ed04eeda15b61ecb40046241"}}}}