{"action":"created","comment":{"url":"https://api.github.com/repos/heroku/shh/pulls/comments/42910647","id":42910647,"diff_hunk":"@@ -0,0 +1,99 @@\n+package shh\n+\n+import (\n+\t\"io/ioutil\"\n+\t\"strings\"\n+\t\"time\"\n+\n+\t\"github.com/heroku/shh/Godeps/_workspace/src/github.com/freeformz/filechan\"\n+)\n+\n+const (\n+\tCGROUPS_PATH = \"/sys/fs/cgroup\"\n+)\n+\n+type Cgroup struct {\n+\tmeasurements chan<- Measurement\n+\tlast         map[string]uint64\n+\tcgroups      []string\n+\t// The kernel will report CPU usage in centiseconds.  This\n+\t// stores the total centiseconds in the polling interval.\n+\ttotalCentis uint64\n+}\n+\n+func NewCgroupPoller(measurements chan<- Measurement, config Config) Cgroup {\n+\treturn Cgroup{\n+\t\tmeasurements: measurements,\n+\t\tlast:         make(map[string]uint64),\n+\t\tcgroups:      config.Cgroups,\n+\t\t// convert the interval to centiseconds\n+\t\ttotalCentis: uint64(config.Interval.Nanoseconds() / 10000000),\n+\t}\n+}\n+\n+func sanitizeMetricName(name string) string {\n+\treturn strings.Replace(strings.Replace(name, \".\", \"-\", -1), \"/\", \"--\", -1)\n+}\n+\n+// parsePercent parses a line from cpuacct.stat like \"user 12345678\",\n+// calculates the delta from the last measurement, calculates the\n+// average percentage of one CPU core used, and submits the data point.\n+func (poller Cgroup) parsePercentCpu(line string, tick time.Time, cgroup string) {\n+\tfields := strings.Fields(line)\n+\n+\t// \"user\" or \"system\"\n+\tmetric := fields[0]\n+\n+\t// absolute number of centiseconds of CPU time used\n+\tcentis := Atouint64(fields[1])\n+\n+        key := cgroup + \".\" + metric\n+\tlast, exists := poller.last[key]\n+\n+\tif exists {\n+\t\tdelta := centis - last\n+\t\tpercent := float64(delta) * 100.0 / float64(poller.totalCentis)\n+\n+\t\tpoller.measurements <- FloatGaugeMeasurement{tick, poller.Name(), []string{sanitizeMetricName(cgroup), \"cpu\", metric}, percent, Percent}\n+\t}\n+\n+\tpoller.last[key] = centis\n+}\n+\n+func (poller Cgroup) parseMaxMemory(metric string, fileName string, tick time.Time, cgroup string) {\n+\tpath := CGROUPS_PATH + \"/memory/\" + cgroup + \"/\" + fileName\n+\tdata, err := ioutil.ReadFile(path)\n+\n+\tif err == nil {\n+\t\tmaxBytes := Atofloat64(strings.TrimSpace(string(data)))\n+\t\tpoller.measurements <- FloatGaugeMeasurement{tick, poller.Name(), []string{sanitizeMetricName(cgroup), \"mem\", metric}, maxBytes, Bytes}\n+\n+\t\t// reset the high water mark\n+\t\tioutil.WriteFile(path, []byte(\"0\"), 0644)\n+\t}\n+}\n+\n+func (poller Cgroup) Poll(tick time.Time) {\n+\tfor _, cgroup := range poller.cgroups {\n+\t\t// I can't use the FileLineChannel here because I don't want","path":"cgroup_poller.go","position":78,"original_position":78,"commit_id":"bdeab74e516f674ade063304ebadaa4c65c7bbce","original_commit_id":"bdeab74e516f674ade063304ebadaa4c65c7bbce","user":{"login":"apg","id":44547,"avatar_url":"https://avatars.githubusercontent.com/u/44547?v=3","gravatar_id":"","url":"https://api.github.com/users/apg","html_url":"https://github.com/apg","followers_url":"https://api.github.com/users/apg/followers","following_url":"https://api.github.com/users/apg/following{/other_user}","gists_url":"https://api.github.com/users/apg/gists{/gist_id}","starred_url":"https://api.github.com/users/apg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apg/subscriptions","organizations_url":"https://api.github.com/users/apg/orgs","repos_url":"https://api.github.com/users/apg/repos","events_url":"https://api.github.com/users/apg/events{/privacy}","received_events_url":"https://api.github.com/users/apg/received_events","type":"User","site_admin":false},"body":"Is this comment still relevant? You're using it right below.","created_at":"2015-10-23T20:27:14Z","updated_at":"2015-10-23T20:27:14Z","html_url":"https://github.com/heroku/shh/pull/47#discussion_r42910647","pull_request_url":"https://api.github.com/repos/heroku/shh/pulls/47","_links":{"self":{"href":"https://api.github.com/repos/heroku/shh/pulls/comments/42910647"},"html":{"href":"https://github.com/heroku/shh/pull/47#discussion_r42910647"},"pull_request":{"href":"https://api.github.com/repos/heroku/shh/pulls/47"}}},"pull_request":{"url":"https://api.github.com/repos/heroku/shh/pulls/47","id":48592023,"html_url":"https://github.com/heroku/shh/pull/47","diff_url":"https://github.com/heroku/shh/pull/47.diff","patch_url":"https://github.com/heroku/shh/pull/47.patch","issue_url":"https://api.github.com/repos/heroku/shh/issues/47","number":47,"state":"open","locked":false,"title":"Cgroup poller","user":{"login":"lexelby","id":4446653,"avatar_url":"https://avatars.githubusercontent.com/u/4446653?v=3","gravatar_id":"","url":"https://api.github.com/users/lexelby","html_url":"https://github.com/lexelby","followers_url":"https://api.github.com/users/lexelby/followers","following_url":"https://api.github.com/users/lexelby/following{/other_user}","gists_url":"https://api.github.com/users/lexelby/gists{/gist_id}","starred_url":"https://api.github.com/users/lexelby/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lexelby/subscriptions","organizations_url":"https://api.github.com/users/lexelby/orgs","repos_url":"https://api.github.com/users/lexelby/repos","events_url":"https://api.github.com/users/lexelby/events{/privacy}","received_events_url":"https://api.github.com/users/lexelby/received_events","type":"User","site_admin":false},"body":"This is my first ever Go code, so please excuse any messes :)\r\n\r\nThis adds a poller that retrieves resource usage metrics for a list of cgroups.  It fetches information for the processes from two cgroup controllers: cpuacct and memory.\r\n\r\nFrom cpuacct, it fetches user and system CPU time used by the group expressed as a percentage of a single core.  For example, a value of 125 would mean that process in the group used CPU time equivalent to one and one quarter of a CPU core for the entire polling interval.\r\n\r\nFrom memory, it fetches the MAXIMUM memory usage of the group over the entire interval (the high-water mark).  It attempts to request that the kernel reset this value after each poll by writing a zero to the `memory.max_memory_usage` file in the group's directory.  This will only work if the SHH process has permissions to the file.  You can simply chown/chgrp the file appropriately to grant that access.  Failing to do this will result in this poller reporting only the historical maximum memory usage irrespective of the interval.\r\n\r\nThree memory high-water marks are reported: \"user\", \"kernel\", and \"tcp\".  \"user\" is standard memory usage (RSS).  \"Kernel\" is kernel buffers and the like required by system calls made by processes in the group.  \"tcp\" is just kernel memory required for TCP sessions.  \"Kernel\" and \"tcp\" will always report 0 unless a respective memory limit for the cgroup has been set, due to limitations in the memory cgroup controller.\r\n\r\nI've attempted to write this thing as best I can using Go and SHH idioms.  I opted for allowing cgroups to be specified as an environment variable rather than hardcoding the one we care about in an attempt to make this thing useful for anyone but us ;)  Please let me know if the code needs any tidying/restructuring and I'll do my best to accommodate.","created_at":"2015-10-23T15:12:24Z","updated_at":"2015-10-23T20:27:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"cfdac1b1826210e9c06529051f4902a2956757cb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/heroku/shh/pulls/47/commits","review_comments_url":"https://api.github.com/repos/heroku/shh/pulls/47/comments","review_comment_url":"https://api.github.com/repos/heroku/shh/pulls/comments{/number}","comments_url":"https://api.github.com/repos/heroku/shh/issues/47/comments","statuses_url":"https://api.github.com/repos/heroku/shh/statuses/bdeab74e516f674ade063304ebadaa4c65c7bbce","head":{"label":"heroku:cgroup-poller","ref":"cgroup-poller","sha":"bdeab74e516f674ade063304ebadaa4c65c7bbce","user":{"login":"heroku","id":23211,"avatar_url":"https://avatars.githubusercontent.com/u/23211?v=3","gravatar_id":"","url":"https://api.github.com/users/heroku","html_url":"https://github.com/heroku","followers_url":"https://api.github.com/users/heroku/followers","following_url":"https://api.github.com/users/heroku/following{/other_user}","gists_url":"https://api.github.com/users/heroku/gists{/gist_id}","starred_url":"https://api.github.com/users/heroku/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/heroku/subscriptions","organizations_url":"https://api.github.com/users/heroku/orgs","repos_url":"https://api.github.com/users/heroku/repos","events_url":"https://api.github.com/users/heroku/events{/privacy}","received_events_url":"https://api.github.com/users/heroku/received_events","type":"Organization","site_admin":false},"repo":{"id":8345816,"name":"shh","full_name":"heroku/shh","owner":{"login":"heroku","id":23211,"avatar_url":"https://avatars.githubusercontent.com/u/23211?v=3","gravatar_id":"","url":"https://api.github.com/users/heroku","html_url":"https://github.com/heroku","followers_url":"https://api.github.com/users/heroku/followers","following_url":"https://api.github.com/users/heroku/following{/other_user}","gists_url":"https://api.github.com/users/heroku/gists{/gist_id}","starred_url":"https://api.github.com/users/heroku/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/heroku/subscriptions","organizations_url":"https://api.github.com/users/heroku/orgs","repos_url":"https://api.github.com/users/heroku/repos","events_url":"https://api.github.com/users/heroku/events{/privacy}","received_events_url":"https://api.github.com/users/heroku/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/heroku/shh","description":"Shh - Systems Heuristics Herald","fork":false,"url":"https://api.github.com/repos/heroku/shh","forks_url":"https://api.github.com/repos/heroku/shh/forks","keys_url":"https://api.github.com/repos/heroku/shh/keys{/key_id}","collaborators_url":"https://api.github.com/repos/heroku/shh/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/heroku/shh/teams","hooks_url":"https://api.github.com/repos/heroku/shh/hooks","issue_events_url":"https://api.github.com/repos/heroku/shh/issues/events{/number}","events_url":"https://api.github.com/repos/heroku/shh/events","assignees_url":"https://api.github.com/repos/heroku/shh/assignees{/user}","branches_url":"https://api.github.com/repos/heroku/shh/branches{/branch}","tags_url":"https://api.github.com/repos/heroku/shh/tags","blobs_url":"https://api.github.com/repos/heroku/shh/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/heroku/shh/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/heroku/shh/git/refs{/sha}","trees_url":"https://api.github.com/repos/heroku/shh/git/trees{/sha}","statuses_url":"https://api.github.com/repos/heroku/shh/statuses/{sha}","languages_url":"https://api.github.com/repos/heroku/shh/languages","stargazers_url":"https://api.github.com/repos/heroku/shh/stargazers","contributors_url":"https://api.github.com/repos/heroku/shh/contributors","subscribers_url":"https://api.github.com/repos/heroku/shh/subscribers","subscription_url":"https://api.github.com/repos/heroku/shh/subscription","commits_url":"https://api.github.com/repos/heroku/shh/commits{/sha}","git_commits_url":"https://api.github.com/repos/heroku/shh/git/commits{/sha}","comments_url":"https://api.github.com/repos/heroku/shh/comments{/number}","issue_comment_url":"https://api.github.com/repos/heroku/shh/issues/comments{/number}","contents_url":"https://api.github.com/repos/heroku/shh/contents/{+path}","compare_url":"https://api.github.com/repos/heroku/shh/compare/{base}...{head}","merges_url":"https://api.github.com/repos/heroku/shh/merges","archive_url":"https://api.github.com/repos/heroku/shh/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/heroku/shh/downloads","issues_url":"https://api.github.com/repos/heroku/shh/issues{/number}","pulls_url":"https://api.github.com/repos/heroku/shh/pulls{/number}","milestones_url":"https://api.github.com/repos/heroku/shh/milestones{/number}","notifications_url":"https://api.github.com/repos/heroku/shh/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/heroku/shh/labels{/name}","releases_url":"https://api.github.com/repos/heroku/shh/releases{/id}","created_at":"2013-02-21T22:51:36Z","updated_at":"2015-09-14T11:05:29Z","pushed_at":"2015-10-23T15:21:02Z","git_url":"git://github.com/heroku/shh.git","ssh_url":"git@github.com:heroku/shh.git","clone_url":"https://github.com/heroku/shh.git","svn_url":"https://github.com/heroku/shh","homepage":"","size":1281,"stargazers_count":24,"watchers_count":24,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":9,"mirror_url":null,"open_issues_count":5,"forks":9,"open_issues":5,"watchers":24,"default_branch":"master"}},"base":{"label":"heroku:master","ref":"master","sha":"58f03f866968dc66da66a9b2a67b403f5a53d2ad","user":{"login":"heroku","id":23211,"avatar_url":"https://avatars.githubusercontent.com/u/23211?v=3","gravatar_id":"","url":"https://api.github.com/users/heroku","html_url":"https://github.com/heroku","followers_url":"https://api.github.com/users/heroku/followers","following_url":"https://api.github.com/users/heroku/following{/other_user}","gists_url":"https://api.github.com/users/heroku/gists{/gist_id}","starred_url":"https://api.github.com/users/heroku/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/heroku/subscriptions","organizations_url":"https://api.github.com/users/heroku/orgs","repos_url":"https://api.github.com/users/heroku/repos","events_url":"https://api.github.com/users/heroku/events{/privacy}","received_events_url":"https://api.github.com/users/heroku/received_events","type":"Organization","site_admin":false},"repo":{"id":8345816,"name":"shh","full_name":"heroku/shh","owner":{"login":"heroku","id":23211,"avatar_url":"https://avatars.githubusercontent.com/u/23211?v=3","gravatar_id":"","url":"https://api.github.com/users/heroku","html_url":"https://github.com/heroku","followers_url":"https://api.github.com/users/heroku/followers","following_url":"https://api.github.com/users/heroku/following{/other_user}","gists_url":"https://api.github.com/users/heroku/gists{/gist_id}","starred_url":"https://api.github.com/users/heroku/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/heroku/subscriptions","organizations_url":"https://api.github.com/users/heroku/orgs","repos_url":"https://api.github.com/users/heroku/repos","events_url":"https://api.github.com/users/heroku/events{/privacy}","received_events_url":"https://api.github.com/users/heroku/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/heroku/shh","description":"Shh - Systems Heuristics Herald","fork":false,"url":"https://api.github.com/repos/heroku/shh","forks_url":"https://api.github.com/repos/heroku/shh/forks","keys_url":"https://api.github.com/repos/heroku/shh/keys{/key_id}","collaborators_url":"https://api.github.com/repos/heroku/shh/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/heroku/shh/teams","hooks_url":"https://api.github.com/repos/heroku/shh/hooks","issue_events_url":"https://api.github.com/repos/heroku/shh/issues/events{/number}","events_url":"https://api.github.com/repos/heroku/shh/events","assignees_url":"https://api.github.com/repos/heroku/shh/assignees{/user}","branches_url":"https://api.github.com/repos/heroku/shh/branches{/branch}","tags_url":"https://api.github.com/repos/heroku/shh/tags","blobs_url":"https://api.github.com/repos/heroku/shh/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/heroku/shh/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/heroku/shh/git/refs{/sha}","trees_url":"https://api.github.com/repos/heroku/shh/git/trees{/sha}","statuses_url":"https://api.github.com/repos/heroku/shh/statuses/{sha}","languages_url":"https://api.github.com/repos/heroku/shh/languages","stargazers_url":"https://api.github.com/repos/heroku/shh/stargazers","contributors_url":"https://api.github.com/repos/heroku/shh/contributors","subscribers_url":"https://api.github.com/repos/heroku/shh/subscribers","subscription_url":"https://api.github.com/repos/heroku/shh/subscription","commits_url":"https://api.github.com/repos/heroku/shh/commits{/sha}","git_commits_url":"https://api.github.com/repos/heroku/shh/git/commits{/sha}","comments_url":"https://api.github.com/repos/heroku/shh/comments{/number}","issue_comment_url":"https://api.github.com/repos/heroku/shh/issues/comments{/number}","contents_url":"https://api.github.com/repos/heroku/shh/contents/{+path}","compare_url":"https://api.github.com/repos/heroku/shh/compare/{base}...{head}","merges_url":"https://api.github.com/repos/heroku/shh/merges","archive_url":"https://api.github.com/repos/heroku/shh/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/heroku/shh/downloads","issues_url":"https://api.github.com/repos/heroku/shh/issues{/number}","pulls_url":"https://api.github.com/repos/heroku/shh/pulls{/number}","milestones_url":"https://api.github.com/repos/heroku/shh/milestones{/number}","notifications_url":"https://api.github.com/repos/heroku/shh/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/heroku/shh/labels{/name}","releases_url":"https://api.github.com/repos/heroku/shh/releases{/id}","created_at":"2013-02-21T22:51:36Z","updated_at":"2015-09-14T11:05:29Z","pushed_at":"2015-10-23T15:21:02Z","git_url":"git://github.com/heroku/shh.git","ssh_url":"git@github.com:heroku/shh.git","clone_url":"https://github.com/heroku/shh.git","svn_url":"https://github.com/heroku/shh","homepage":"","size":1281,"stargazers_count":24,"watchers_count":24,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":9,"mirror_url":null,"open_issues_count":5,"forks":9,"open_issues":5,"watchers":24,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/heroku/shh/pulls/47"},"html":{"href":"https://github.com/heroku/shh/pull/47"},"issue":{"href":"https://api.github.com/repos/heroku/shh/issues/47"},"comments":{"href":"https://api.github.com/repos/heroku/shh/issues/47/comments"},"review_comments":{"href":"https://api.github.com/repos/heroku/shh/pulls/47/comments"},"review_comment":{"href":"https://api.github.com/repos/heroku/shh/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/heroku/shh/pulls/47/commits"},"statuses":{"href":"https://api.github.com/repos/heroku/shh/statuses/bdeab74e516f674ade063304ebadaa4c65c7bbce"}}}}