{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/heapster/pulls/comments/36901356","id":36901356,"diff_hunk":"@@ -0,0 +1,486 @@\n+// Copyright 2015 Google Inc. All Rights Reserved.\n+//\n+// Licensed under the Apache License, Version 2.0 (the \"License\");\n+// you may not use this file except in compliance with the License.\n+// You may obtain a copy of the License at\n+//\n+//     http://www.apache.org/licenses/LICENSE-2.0\n+//\n+// Unless required by applicable law or agreed to in writing, software\n+// distributed under the License is distributed on an \"AS IS\" BASIS,\n+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+// See the License for the specific language governing permissions and\n+// limitations under the License.\n+\n+package store\n+\n+import (\n+\t\"container/list\"\n+\t\"fmt\"\n+\t\"math\"\n+\t\"sort\"\n+\t\"sync\"\n+\t\"time\"\n+)\n+\n+// StatStore is an in-memory rolling timeseries window that allows the extraction of -\n+// segments of the stored timeseries and derived stats.\n+// StatStore only retains information about the latest TimePoints that are within its\n+// specified duration.\n+\n+// The tradeoff between space and precision can be configured through the epsilon and resolution -\n+// parameters.\n+// @epsilon: the acceptable error margin, in absolute value, such as 1024 bytes.\n+// @resolution: the desired resolution of the StatStore, such as 2 * time.Minute\n+\n+// Compression in the StatStore is performed by storing values of consecutive time resolutions -\n+// that differ less than epsilon in the same \"bucket\", represented by the tpBucket struct.\n+\n+// For example, a timeseries may be represented in the following manner.\n+// Each line parallel to the x-axis represents a single tpBucket.\n+// This example leads to 4 tpBuckets being stored in the StatStore, even though the length of the\n+// window is 7 resolutions.\n+//\n+// Legend:\n+//\tε : epsilon\n+//\tδ : resolution\n+//      W : window length\n+//\n+//        value ^\n+//\t\t|\n+//\t\t|\n+//\t     4ε |      \t ----\n+//\t     \t|\t |  |\n+//\t     2ε |--------|  |\t\t |----|\n+//\t      ε\t|\t    |------------|\n+//\t\t|_____________________________|______>\n+//\t\t\t\t\t       W   time\n+//\t\t|--------|--|------------|----|\n+//\t\t   2δ     δ       3δ        δ\n+\n+// StatStore assumes that values are inserted in a chronologically ascending order through the -\n+// Put method. If a TimePoint with a past Timestamp is inserted, it is ignored.\n+// The last one resolution's worth of Timepoints are held in a putState structure, as -\n+// we are not confident of that resolution's average until values from the next resolution have -\n+// arrived. Due to this assumption, the data extraction methods of the StatStore ignore values -\n+// currently in the putState struct.\n+\n+func NewStatStore(epsilon uint64, resolution time.Duration, windowDuration uint8, supportedPercentiles []float64) *StatStore {\n+\treturn &StatStore{\n+\t\tbuffer:               list.New(),\n+\t\tepsilon:              epsilon,\n+\t\tresolution:           resolution,\n+\t\twindowDuration:       windowDuration,\n+\t\tsupportedPercentiles: supportedPercentiles,\n+\t}\n+}\n+\n+// TimePoint is a single point of a timeseries, representing a time-value pair.\n+type TimePoint struct {\n+\tTimestamp time.Time\n+\tValue     uint64\n+}\n+\n+// StatStore is a TimeStore-like object that does not implement the TimeStore interface.\n+type StatStore struct {\n+\t// start is the start of the represented time window.\n+\tstart time.Time\n+\n+\t// buffer is a list of tpBucket that is sequenced in a time-descending order, meaning that\n+\t// Front points to the latest tpBucket and Back to the oldest one.\n+\tbuffer *list.List\n+\n+\t// A RWMutex guards all operations on the StatStore.\n+\tsync.RWMutex\n+\n+\t// epsilon is the acceptable error difference for the storage of TimePoints.\n+\t// Increasing epsilon decreases memory usage of the StatStore, at the cost of precision.\n+\t// The precision of max is not affected by epsilon.\n+\tepsilon uint64\n+\n+\t// resolution is the standardized duration between points in the StatStore.\n+\t// the Get operation returns TimePoints at every multiple of resolution,\n+\t// even if TimePoints have not been Put for all such times.\n+\tresolution time.Duration\n+\n+\t// windowDuration is the maximum number of time resolutions that is stored in the StatStore\n+\t// e.g. windowDuration 60, with a resolution of time.Minute represents an 1-hour window\n+\twindowDuration uint8\n+\n+\t// tpCount is the number of TimePoints that are represented in the StatStore.\n+\t// Ff tpCount is equal to windowDuration, then the StatStore window is considered full.\n+\ttpCount uint8\n+\n+\t// lastPut maintains the state of values inserted within the last resolution.\n+\t// When values of a later resolution are added to the StatStore, lastPut is flushed to\n+\t// the last tpBucket, if its average is within epsilon. Otherwise, a new bucket is -\n+\t// created.\n+\tlastPut putState\n+\n+\t// suportedPercentiles is a slice of values from (0,1) that represents the percentiles\n+\t// that are calculated by the StatStore.\n+\tsupportedPercentiles []float64\n+\n+\t// validCache is true if lastPut has not been flushed since the calculation of the -\n+\t// cached derived stats.\n+\tvalidCache bool\n+\n+\t// cachedAverage, cachedMax and cachedPercentiles are the cached derived stats that -\n+\t// are exposed by the StatStore. They are calculated upon the first request of any -\n+\t// derived stat, and invalidated when lastPut is flushed into the StatStore\n+\tcachedAverage     uint64\n+\tcachedMax         uint64\n+\tcachedPercentiles []uint64\n+}\n+\n+// tpBucket is a bucket that represents a set of consecutive TimePoints with different\n+// timestamps whose values differ less than epsilon.\n+// tpBucket essentially represents a time window with a constant value.\n+type tpBucket struct {\n+\t// count is the number of TimePoints represented in the tpBucket.\n+\tcount uint8\n+\n+\t// value is the approximate value of all in the tpBucket, +- epsilon.\n+\tvalue uint64\n+\n+\t// max is the maximum value of all TimePoints that have been used to generate the tpBucket.\n+\tmax uint64\n+\n+\t// maxIdx is the number of resolutions after the start time where the max value is located.\n+\tmaxIdx uint8\n+}\n+\n+// putState is a structure that maintains context of the values in the resolution that is currently\n+// being inserted.\n+// Assumes that Puts are performed in a time-ascending order.\n+type putState struct {\n+\tactualCount uint16\n+\taverage     float64\n+\tmax         uint64\n+\tstamp       time.Time\n+}\n+\n+func (ss *StatStore) Put(tp TimePoint) error {\n+\tss.Lock()\n+\tdefer ss.Unlock()\n+\n+\t// Flatten timestamp to the last multiple of resolution\n+\tts := tp.Timestamp.Truncate(ss.resolution)\n+\n+\tlastPutTime := ss.lastPut.stamp\n+\n+\t// Handle the case where the buffer and lastPut are both empty\n+\tif lastPutTime.Equal(time.Time{}) {\n+\t\tss.resetLastPut(ts, tp.Value)\n+\t\treturn nil\n+\t}\n+\n+\tif ts.Before(lastPutTime) {\n+\t\t// Ignore TimePoints with Timestamps in the past\n+\t\treturn fmt.Errorf(\"the provided timepoint has a timestamp in the past\")\n+\t}\n+\n+\tif ts.Equal(lastPutTime) {\n+\t\t// update lastPut with the new TimePoint\n+\t\tnewVal := tp.Value\n+\t\tif newVal > ss.lastPut.max {\n+\t\t\tss.lastPut.max = newVal\n+\t\t}\n+\t\toldAvg := ss.lastPut.average\n+\t\tn := float64(ss.lastPut.actualCount)\n+\t\tss.lastPut.average = (float64(newVal) + (n * oldAvg)) / (n + 1)\n+\t\tss.lastPut.actualCount++\n+\t\treturn nil\n+\t}\n+\n+\t// The new point is in the future, lastPut needs to be flushed to the StatStore.\n+\tss.validCache = false\n+\n+\t// Determine how many resolutions in the future the new point is at.\n+\t// The StatStore always represents values up until 1 resolution from lastPut.\n+\t// If the TimePoint is more than one resolutions in the future, the last bucket is -\n+\t// extended to be exactly one resolution behind the new lastPut timestamp.\n+\tnumRes := uint8(0)\n+\tcurr := ts\n+\tfor curr.After(ss.lastPut.stamp) {\n+\t\tcurr = curr.Add(-ss.resolution)\n+\t\tnumRes++\n+\t}\n+\n+\t// Create a new bucket if the buffer is empty\n+\tif ss.buffer.Front() == nil {\n+\t\tss.newBucket(numRes)\n+\t\tss.resetLastPut(ts, tp.Value)\n+\t\tfor ss.tpCount > ss.windowDuration {\n+\t\t\tss.rewind()\n+\t\t}\n+\t\treturn nil\n+\t}\n+\n+\tlastEntry := ss.buffer.Front().Value.(tpBucket)\n+\tlastAvg := ss.lastPut.average\n+\n+\t// Place lastPut in the latest bucket if the difference from its average\n+\t// is less than epsilon\n+\tif uint64(math.Abs(float64(lastEntry.value)-lastAvg)) < ss.epsilon {\n+\t\tlastEntry.count += numRes\n+\t\tss.tpCount += numRes\n+\t\tif ss.lastPut.max > lastEntry.max {\n+\t\t\tlastEntry.max = ss.lastPut.max\n+\t\t\tlastEntry.maxIdx = lastEntry.count - 1\n+\t\t}\n+\t\t// update in list\n+\t\tss.buffer.Front().Value = lastEntry\n+\t} else {\n+\t\t// Create a new bucket\n+\t\tss.newBucket(numRes)\n+\t}\n+\n+\t// Delete the earliest represented TimePoints if the window is full\n+\tfor ss.tpCount > ss.windowDuration {\n+\t\tss.rewind()\n+\t}\n+\n+\tss.resetLastPut(ts, tp.Value)\n+\treturn nil\n+}\n+\n+// resetLastPut initializes the lastPut field of the StatStore, given a time and a value.\n+func (ss *StatStore) resetLastPut(timestamp time.Time, value uint64) {\n+\tss.lastPut.stamp = timestamp\n+\tss.lastPut.actualCount = 1\n+\tss.lastPut.average = float64(value)\n+\tss.lastPut.max = value\n+}\n+\n+// newBucket appends a new bucket to the StatStore, using the values of lastPut.\n+// newBucket should be always called BEFORE resetting lastPut.\n+// newBuckets are created by rounding up the lastPut average to the closest epsilon.\n+// numRes represents the number of resolutions from the newest TimePoint to the lastPut.\n+// numRes resolutions will be represented in the newly created bucket.\n+func (ss *StatStore) newBucket(numRes uint8) {\n+\tnewEntry := tpBucket{\n+\t\tcount:  numRes,\n+\t\tvalue:  ((uint64(ss.lastPut.average) / ss.epsilon) + 1) * ss.epsilon,\n+\t\tmax:    ss.lastPut.max,\n+\t\tmaxIdx: 0,\n+\t}\n+\tss.buffer.PushFront(newEntry)\n+\tss.tpCount += numRes\n+\n+\t// If this was the first bucket, update ss.start\n+\tif ss.start.Equal(time.Time{}) {\n+\t\tss.start = ss.lastPut.stamp\n+\t}\n+}\n+\n+// rewind deletes the oldest one resolution of data in the StatStore.\n+func (ss *StatStore) rewind() {\n+\tfirstElem := ss.buffer.Back()\n+\tfirstEntry := firstElem.Value.(tpBucket)\n+\t// Decrement number of TimePoints in the earliest tpBucket\n+\tfirstEntry.count--\n+\t// Decrement total number of TimePoints in the StatStore\n+\tss.tpCount--\n+\tif firstEntry.maxIdx == 0 {\n+\t\t// The Max value was just removed, lose precision for other maxes in this bucket\n+\t\tfirstEntry.max = firstEntry.value\n+\t\tfirstEntry.maxIdx = firstEntry.count - 1\n+\t} else {\n+\t\tfirstEntry.maxIdx--","path":"store/statstore/stat_store.go","position":290,"original_position":290,"commit_id":"4b70cadbe9d886d18c1857a6ba15fe2ad4531afa","original_commit_id":"4b70cadbe9d886d18c1857a6ba15fe2ad4531afa","user":{"login":"vishh","id":7063592,"avatar_url":"https://avatars.githubusercontent.com/u/7063592?v=3","gravatar_id":"","url":"https://api.github.com/users/vishh","html_url":"https://github.com/vishh","followers_url":"https://api.github.com/users/vishh/followers","following_url":"https://api.github.com/users/vishh/following{/other_user}","gists_url":"https://api.github.com/users/vishh/gists{/gist_id}","starred_url":"https://api.github.com/users/vishh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vishh/subscriptions","organizations_url":"https://api.github.com/users/vishh/orgs","repos_url":"https://api.github.com/users/vishh/repos","events_url":"https://api.github.com/users/vishh/events{/privacy}","received_events_url":"https://api.github.com/users/vishh/received_events","type":"User","site_admin":false},"body":"Why move the maxIdx here? Can you add a small example as comments here?","created_at":"2015-08-12T19:15:16Z","updated_at":"2015-08-12T19:15:16Z","html_url":"https://github.com/kubernetes/heapster/pull/461#discussion_r36901356","pull_request_url":"https://api.github.com/repos/kubernetes/heapster/pulls/461","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/heapster/pulls/comments/36901356"},"html":{"href":"https://github.com/kubernetes/heapster/pull/461#discussion_r36901356"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/heapster/pulls/461"}}},"pull_request":{"url":"https://api.github.com/repos/kubernetes/heapster/pulls/461","id":42043934,"html_url":"https://github.com/kubernetes/heapster/pull/461","diff_url":"https://github.com/kubernetes/heapster/pull/461.diff","patch_url":"https://github.com/kubernetes/heapster/pull/461.patch","issue_url":"https://api.github.com/repos/kubernetes/heapster/issues/461","number":461,"state":"open","locked":false,"title":"Time and value-compressible statStore","user":{"login":"afein","id":680441,"avatar_url":"https://avatars.githubusercontent.com/u/680441?v=3","gravatar_id":"","url":"https://api.github.com/users/afein","html_url":"https://github.com/afein","followers_url":"https://api.github.com/users/afein/followers","following_url":"https://api.github.com/users/afein/following{/other_user}","gists_url":"https://api.github.com/users/afein/gists{/gist_id}","starred_url":"https://api.github.com/users/afein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/afein/subscriptions","organizations_url":"https://api.github.com/users/afein/orgs","repos_url":"https://api.github.com/users/afein/repos","events_url":"https://api.github.com/users/afein/events{/privacy}","received_events_url":"https://api.github.com/users/afein/received_events","type":"User","site_admin":false},"body":"\\cc @vishh\r\n\r\nThis statStore can be configured to make tradeoffs between precision and memory consumption though the epsilon and resolution arguments.","created_at":"2015-08-10T17:20:40Z","updated_at":"2015-08-12T19:15:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b49f12d59df0a8cf534dadddc20bc2caa5480768","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes/heapster/pulls/461/commits","review_comments_url":"https://api.github.com/repos/kubernetes/heapster/pulls/461/comments","review_comment_url":"https://api.github.com/repos/kubernetes/heapster/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/heapster/issues/461/comments","statuses_url":"https://api.github.com/repos/kubernetes/heapster/statuses/4b70cadbe9d886d18c1857a6ba15fe2ad4531afa","head":{"label":"afein:stat_store_struct","ref":"stat_store_struct","sha":"4b70cadbe9d886d18c1857a6ba15fe2ad4531afa","user":{"login":"afein","id":680441,"avatar_url":"https://avatars.githubusercontent.com/u/680441?v=3","gravatar_id":"","url":"https://api.github.com/users/afein","html_url":"https://github.com/afein","followers_url":"https://api.github.com/users/afein/followers","following_url":"https://api.github.com/users/afein/following{/other_user}","gists_url":"https://api.github.com/users/afein/gists{/gist_id}","starred_url":"https://api.github.com/users/afein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/afein/subscriptions","organizations_url":"https://api.github.com/users/afein/orgs","repos_url":"https://api.github.com/users/afein/repos","events_url":"https://api.github.com/users/afein/events{/privacy}","received_events_url":"https://api.github.com/users/afein/received_events","type":"User","site_admin":false},"repo":{"id":36956080,"name":"heapster","full_name":"afein/heapster","owner":{"login":"afein","id":680441,"avatar_url":"https://avatars.githubusercontent.com/u/680441?v=3","gravatar_id":"","url":"https://api.github.com/users/afein","html_url":"https://github.com/afein","followers_url":"https://api.github.com/users/afein/followers","following_url":"https://api.github.com/users/afein/following{/other_user}","gists_url":"https://api.github.com/users/afein/gists{/gist_id}","starred_url":"https://api.github.com/users/afein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/afein/subscriptions","organizations_url":"https://api.github.com/users/afein/orgs","repos_url":"https://api.github.com/users/afein/repos","events_url":"https://api.github.com/users/afein/events{/privacy}","received_events_url":"https://api.github.com/users/afein/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/afein/heapster","description":"Monitor container resource usage of a Kubernetes cluster","fork":true,"url":"https://api.github.com/repos/afein/heapster","forks_url":"https://api.github.com/repos/afein/heapster/forks","keys_url":"https://api.github.com/repos/afein/heapster/keys{/key_id}","collaborators_url":"https://api.github.com/repos/afein/heapster/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/afein/heapster/teams","hooks_url":"https://api.github.com/repos/afein/heapster/hooks","issue_events_url":"https://api.github.com/repos/afein/heapster/issues/events{/number}","events_url":"https://api.github.com/repos/afein/heapster/events","assignees_url":"https://api.github.com/repos/afein/heapster/assignees{/user}","branches_url":"https://api.github.com/repos/afein/heapster/branches{/branch}","tags_url":"https://api.github.com/repos/afein/heapster/tags","blobs_url":"https://api.github.com/repos/afein/heapster/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/afein/heapster/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/afein/heapster/git/refs{/sha}","trees_url":"https://api.github.com/repos/afein/heapster/git/trees{/sha}","statuses_url":"https://api.github.com/repos/afein/heapster/statuses/{sha}","languages_url":"https://api.github.com/repos/afein/heapster/languages","stargazers_url":"https://api.github.com/repos/afein/heapster/stargazers","contributors_url":"https://api.github.com/repos/afein/heapster/contributors","subscribers_url":"https://api.github.com/repos/afein/heapster/subscribers","subscription_url":"https://api.github.com/repos/afein/heapster/subscription","commits_url":"https://api.github.com/repos/afein/heapster/commits{/sha}","git_commits_url":"https://api.github.com/repos/afein/heapster/git/commits{/sha}","comments_url":"https://api.github.com/repos/afein/heapster/comments{/number}","issue_comment_url":"https://api.github.com/repos/afein/heapster/issues/comments{/number}","contents_url":"https://api.github.com/repos/afein/heapster/contents/{+path}","compare_url":"https://api.github.com/repos/afein/heapster/compare/{base}...{head}","merges_url":"https://api.github.com/repos/afein/heapster/merges","archive_url":"https://api.github.com/repos/afein/heapster/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/afein/heapster/downloads","issues_url":"https://api.github.com/repos/afein/heapster/issues{/number}","pulls_url":"https://api.github.com/repos/afein/heapster/pulls{/number}","milestones_url":"https://api.github.com/repos/afein/heapster/milestones{/number}","notifications_url":"https://api.github.com/repos/afein/heapster/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/afein/heapster/labels{/name}","releases_url":"https://api.github.com/repos/afein/heapster/releases{/id}","created_at":"2015-06-05T21:24:01Z","updated_at":"2015-06-05T21:24:02Z","pushed_at":"2015-08-12T18:45:27Z","git_url":"git://github.com/afein/heapster.git","ssh_url":"git@github.com:afein/heapster.git","clone_url":"https://github.com/afein/heapster.git","svn_url":"https://github.com/afein/heapster","homepage":"","size":19970,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"kubernetes:master","ref":"master","sha":"a46624e66bd59c658f2fc7c2fdecedc6c2a3fc9f","user":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars.githubusercontent.com/u/13629408?v=3","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"repo":{"id":23272510,"name":"heapster","full_name":"kubernetes/heapster","owner":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars.githubusercontent.com/u/13629408?v=3","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kubernetes/heapster","description":"Compute Resource Usage Analysis and Monitoring of Container Clusters","fork":false,"url":"https://api.github.com/repos/kubernetes/heapster","forks_url":"https://api.github.com/repos/kubernetes/heapster/forks","keys_url":"https://api.github.com/repos/kubernetes/heapster/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kubernetes/heapster/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kubernetes/heapster/teams","hooks_url":"https://api.github.com/repos/kubernetes/heapster/hooks","issue_events_url":"https://api.github.com/repos/kubernetes/heapster/issues/events{/number}","events_url":"https://api.github.com/repos/kubernetes/heapster/events","assignees_url":"https://api.github.com/repos/kubernetes/heapster/assignees{/user}","branches_url":"https://api.github.com/repos/kubernetes/heapster/branches{/branch}","tags_url":"https://api.github.com/repos/kubernetes/heapster/tags","blobs_url":"https://api.github.com/repos/kubernetes/heapster/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kubernetes/heapster/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kubernetes/heapster/git/refs{/sha}","trees_url":"https://api.github.com/repos/kubernetes/heapster/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kubernetes/heapster/statuses/{sha}","languages_url":"https://api.github.com/repos/kubernetes/heapster/languages","stargazers_url":"https://api.github.com/repos/kubernetes/heapster/stargazers","contributors_url":"https://api.github.com/repos/kubernetes/heapster/contributors","subscribers_url":"https://api.github.com/repos/kubernetes/heapster/subscribers","subscription_url":"https://api.github.com/repos/kubernetes/heapster/subscription","commits_url":"https://api.github.com/repos/kubernetes/heapster/commits{/sha}","git_commits_url":"https://api.github.com/repos/kubernetes/heapster/git/commits{/sha}","comments_url":"https://api.github.com/repos/kubernetes/heapster/comments{/number}","issue_comment_url":"https://api.github.com/repos/kubernetes/heapster/issues/comments{/number}","contents_url":"https://api.github.com/repos/kubernetes/heapster/contents/{+path}","compare_url":"https://api.github.com/repos/kubernetes/heapster/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kubernetes/heapster/merges","archive_url":"https://api.github.com/repos/kubernetes/heapster/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kubernetes/heapster/downloads","issues_url":"https://api.github.com/repos/kubernetes/heapster/issues{/number}","pulls_url":"https://api.github.com/repos/kubernetes/heapster/pulls{/number}","milestones_url":"https://api.github.com/repos/kubernetes/heapster/milestones{/number}","notifications_url":"https://api.github.com/repos/kubernetes/heapster/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kubernetes/heapster/labels{/name}","releases_url":"https://api.github.com/repos/kubernetes/heapster/releases{/id}","created_at":"2014-08-24T03:50:18Z","updated_at":"2015-08-12T14:18:15Z","pushed_at":"2015-08-12T18:45:29Z","git_url":"git://github.com/kubernetes/heapster.git","ssh_url":"git@github.com:kubernetes/heapster.git","clone_url":"https://github.com/kubernetes/heapster.git","svn_url":"https://github.com/kubernetes/heapster","homepage":"","size":22863,"stargazers_count":371,"watchers_count":371,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":99,"mirror_url":null,"open_issues_count":80,"forks":99,"open_issues":80,"watchers":371,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/heapster/pulls/461"},"html":{"href":"https://github.com/kubernetes/heapster/pull/461"},"issue":{"href":"https://api.github.com/repos/kubernetes/heapster/issues/461"},"comments":{"href":"https://api.github.com/repos/kubernetes/heapster/issues/461/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/heapster/pulls/461/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/heapster/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/heapster/pulls/461/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/heapster/statuses/4b70cadbe9d886d18c1857a6ba15fe2ad4531afa"}}}}