{"action":"created","comment":{"url":"https://api.github.com/repos/weaveworks/cortex/pulls/comments/94972229","pull_request_review_id":15517021,"id":94972229,"diff_hunk":"@@ -0,0 +1,347 @@\n+package chunk\n+\n+import (\n+\t\"net/url\"\n+\t\"sort\"\n+\t\"strconv\"\n+\t\"strings\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\t\"github.com/aws/aws-sdk-go/aws\"\n+\t\"github.com/aws/aws-sdk-go/aws/session\"\n+\t\"github.com/aws/aws-sdk-go/service/dynamodb\"\n+\t\"github.com/prometheus/client_golang/prometheus\"\n+\t\"github.com/prometheus/common/log\"\n+\t\"github.com/weaveworks/common/mtime\"\n+\t\"github.com/weaveworks/scope/common/instrument\"\n+\t\"golang.org/x/net/context\"\n+)\n+\n+const (\n+\treadLabel        = \"read\"\n+\twriteLabel       = \"write\"\n+\tminWriteCapacity = 1\n+)\n+\n+var (\n+\tsyncTableDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{\n+\t\tNamespace: \"cortex\",\n+\t\tName:      \"dynamo_sync_tables_seconds\",\n+\t\tHelp:      \"Time spent doing syncTables.\",\n+\t\tBuckets:   prometheus.DefBuckets,\n+\t}, []string{\"operation\", \"status_code\"})\n+\ttableCapacity = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n+\t\tNamespace: \"cortex\",\n+\t\tName:      \"dynamo_table_capacity_units\",\n+\t\tHelp:      \"Per-table DynamoDB capacity, measured in DynamoDB capacity units.\",\n+\t}, []string{\"op\", \"table\"})\n+)\n+\n+func init() {\n+\tprometheus.MustRegister(tableCapacity)\n+}\n+\n+// TableManagerConfig is the config for a DynamoTableManager\n+type TableManagerConfig struct {\n+\tDynamoDBURL          string\n+\tDynamoDBPollInterval time.Duration\n+\n+\tUsePeriodicTables    bool\n+\tTablePrefix          string\n+\tTablePeriod          time.Duration\n+\tPeriodicTableStartAt time.Time\n+\n+\t// duration a table will be created before it is needed.\n+\tCreationGracePeriod        time.Duration\n+\tMaxChunkAge                time.Duration\n+\tProvisionedWriteThroughput int64\n+\tProvisionedReadThroughput  int64\n+\n+\t// Not exported as only used by tests to inject mocks\n+\tdynamodb dynamodbClient\n+}\n+\n+// DynamoTableManager creates and manages the provisioned throughput on DynamoDB tables\n+type DynamoTableManager struct {\n+\tcfg      TableManagerConfig\n+\tdynamodb dynamodbClient\n+\n+\t// the name of the single, pre-weekly table\n+\ttableName string\n+\n+\tdone chan struct{}\n+\twait sync.WaitGroup\n+}\n+\n+// NewDynamoTableManager makes a new DynamoTableManager\n+func NewDynamoTableManager(cfg TableManagerConfig) (*DynamoTableManager, error) {\n+\tdynamodbClient, tableName := cfg.dynamodb, \"\"\n+\tif dynamodbClient == nil {\n+\t\tdynamodbURL, err := url.Parse(cfg.DynamoDBURL)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\n+\t\tdynamoDBConfig, err := awsConfigFromURL(dynamodbURL)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\n+\t\tdynamodbClient = dynamoClientAdapter{dynamodb.New(session.New(dynamoDBConfig))}\n+\t\ttableName = strings.TrimPrefix(dynamodbURL.Path, \"/\")\n+\t}\n+\n+\tm := &DynamoTableManager{\n+\t\tdynamodb:  dynamodbClient,\n+\t\ttableName: tableName,\n+\n+\t\tcfg:  cfg,\n+\t\tdone: make(chan struct{}),\n+\t}\n+\treturn m, nil\n+}\n+\n+// Start starts the DynamoTableManager\n+func (m *DynamoTableManager) Start() {\n+\tm.wait.Add(1)\n+\tgo m.loop()\n+}\n+\n+// Stop stops the DynamoTableManager\n+func (m *DynamoTableManager) Stop() {\n+\tclose(m.done)\n+\tm.wait.Wait()\n+}\n+\n+func (m *DynamoTableManager) loop() {\n+\tdefer m.wait.Done()\n+\n+\tticker := time.NewTicker(m.cfg.DynamoDBPollInterval)\n+\tdefer ticker.Stop()\n+\n+\tif err := instrument.TimeRequestHistogram(context.Background(), \"DynamoTableManager.syncTables\", syncTableDuration, func(ctx context.Context) error {\n+\t\treturn m.syncTables(ctx)\n+\t}); err != nil {\n+\t\tlog.Errorf(\"Error syncing tables: %v\", err)\n+\t}\n+\n+\tfor {\n+\t\tselect {\n+\t\tcase <-ticker.C:\n+\t\t\tif err := instrument.TimeRequestHistogram(context.Background(), \"DynamoTableManager.syncTables\", syncTableDuration, func(ctx context.Context) error {\n+\t\t\t\treturn m.syncTables(ctx)\n+\t\t\t}); err != nil {\n+\t\t\t\tlog.Errorf(\"Error syncing tables: %v\", err)\n+\t\t\t}\n+\t\tcase <-m.done:\n+\t\t\treturn\n+\t\t}\n+\t}\n+}\n+\n+func (m *DynamoTableManager) syncTables(ctx context.Context) error {\n+\texpected := m.calculateExpectedTables(ctx)\n+\n+\ttoCreate, toCheckThroughput, err := m.partitionTables(ctx, expected)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tif err := m.createTables(ctx, toCreate); err != nil {\n+\t\treturn err\n+\t}\n+\n+\treturn m.updateTables(ctx, toCheckThroughput)\n+}\n+\n+type tableDescription struct {\n+\tname             string\n+\tprovisionedRead  int64\n+\tprovisionedWrite int64\n+}\n+\n+type byName []tableDescription\n+\n+func (a byName) Len() int           { return len(a) }\n+func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\n+func (a byName) Less(i, j int) bool { return a[i].name < a[j].name }\n+\n+func (m *DynamoTableManager) calculateExpectedTables(_ context.Context) []tableDescription {\n+\tif !m.cfg.UsePeriodicTables {\n+\t\treturn []tableDescription{\n+\t\t\t{\n+\t\t\t\tname:             m.tableName,\n+\t\t\t\tprovisionedRead:  m.cfg.ProvisionedReadThroughput,\n+\t\t\t\tprovisionedWrite: m.cfg.ProvisionedWriteThroughput,\n+\t\t\t},\n+\t\t}\n+\t}\n+\n+\tresult := []tableDescription{}\n+\n+\tvar (\n+\t\ttablePeriodSecs = int64(m.cfg.TablePeriod / time.Second)\n+\t\tgracePeriodSecs = int64(m.cfg.CreationGracePeriod / time.Second)\n+\t\tmaxChunkAgeSecs = int64(m.cfg.MaxChunkAge / time.Second)\n+\t\tfirstTable      = m.cfg.PeriodicTableStartAt.Unix() / tablePeriodSecs\n+\t\tlastTable       = (mtime.Now().Unix() + gracePeriodSecs) / tablePeriodSecs\n+\t\tnow             = mtime.Now().Unix()\n+\t)\n+\n+\t// Add the legacy table\n+\t{\n+\t\tlegacyTable := tableDescription{\n+\t\t\tname:             m.tableName,\n+\t\t\tprovisionedRead:  m.cfg.ProvisionedReadThroughput,\n+\t\t\tprovisionedWrite: minWriteCapacity,\n+\t\t}\n+\n+\t\t// if we are before the switch to periodic table, we need to give this table write throughput\n+\t\tif now < (firstTable*tablePeriodSecs)+gracePeriodSecs+maxChunkAgeSecs {\n+\t\t\tlegacyTable.provisionedWrite = m.cfg.ProvisionedWriteThroughput\n+\t\t}\n+\t\tresult = append(result, legacyTable)\n+\t}\n+\n+\tfor i := firstTable; i <= lastTable; i++ {\n+\t\ttable := tableDescription{\n+\t\t\t// Name construction needs to be consistent with chunk_store.bigBuckets","path":"chunk/dynamo_table_manager.go","position":206,"original_position":209,"commit_id":"030bdb83dcfde4e8a7180beb70d3708c20316b75","original_commit_id":"417edc4192cc7cf83924886eaa0549c4ac0799be","user":{"login":"jml","id":125674,"avatar_url":"https://avatars.githubusercontent.com/u/125674?v=3","gravatar_id":"","url":"https://api.github.com/users/jml","html_url":"https://github.com/jml","followers_url":"https://api.github.com/users/jml/followers","following_url":"https://api.github.com/users/jml/following{/other_user}","gists_url":"https://api.github.com/users/jml/gists{/gist_id}","starred_url":"https://api.github.com/users/jml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jml/subscriptions","organizations_url":"https://api.github.com/users/jml/orgs","repos_url":"https://api.github.com/users/jml/repos","events_url":"https://api.github.com/users/jml/events{/privacy}","received_events_url":"https://api.github.com/users/jml/received_events","type":"User","site_admin":false},"body":"Not 100% sure, and getting sure would probably require doing the refactoring myself. ","created_at":"2017-01-06T16:23:13Z","updated_at":"2017-01-06T16:23:13Z","html_url":"https://github.com/weaveworks/cortex/pull/181#discussion_r94972229","pull_request_url":"https://api.github.com/repos/weaveworks/cortex/pulls/181","_links":{"self":{"href":"https://api.github.com/repos/weaveworks/cortex/pulls/comments/94972229"},"html":{"href":"https://github.com/weaveworks/cortex/pull/181#discussion_r94972229"},"pull_request":{"href":"https://api.github.com/repos/weaveworks/cortex/pulls/181"}}},"pull_request":{"url":"https://api.github.com/repos/weaveworks/cortex/pulls/181","id":100102635,"html_url":"https://github.com/weaveworks/cortex/pull/181","diff_url":"https://github.com/weaveworks/cortex/pull/181.diff","patch_url":"https://github.com/weaveworks/cortex/pull/181.patch","issue_url":"https://api.github.com/repos/weaveworks/cortex/issues/181","number":181,"state":"open","locked":false,"title":"Use weekly tables in the chunk store","user":{"login":"tomwilkie","id":444037,"avatar_url":"https://avatars.githubusercontent.com/u/444037?v=3","gravatar_id":"","url":"https://api.github.com/users/tomwilkie","html_url":"https://github.com/tomwilkie","followers_url":"https://api.github.com/users/tomwilkie/followers","following_url":"https://api.github.com/users/tomwilkie/following{/other_user}","gists_url":"https://api.github.com/users/tomwilkie/gists{/gist_id}","starred_url":"https://api.github.com/users/tomwilkie/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomwilkie/subscriptions","organizations_url":"https://api.github.com/users/tomwilkie/orgs","repos_url":"https://api.github.com/users/tomwilkie/repos","events_url":"https://api.github.com/users/tomwilkie/events{/privacy}","received_events_url":"https://api.github.com/users/tomwilkie/received_events","type":"User","site_admin":false},"body":"Part of #158\r\n\r\nIntroduces a new job, the table manager, which periodically calculates the required tables (and provisioned throughputs) and then creates / updates the existing tables as appropriate.\r\n\r\nAlso update the chunk store to calculate the tables a given bucket should be written to.\r\n\r\nTODO:\r\n- [x] update `bigBuckets` to also pick a table for the bucket\r\n- [x] new binary/container to create / update tables\r\n- [x] unit test\r\n- [x] add some monitoring for the dynamodb operations.\r\n- [x] figure out when we can correctly remove write capacity from a table (see #182)","created_at":"2017-01-04T11:47:10Z","updated_at":"2017-01-06T16:23:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"648a93d98dcc11d635451557ef20eaab9d4fc96e","assignee":{"login":"tomwilkie","id":444037,"avatar_url":"https://avatars.githubusercontent.com/u/444037?v=3","gravatar_id":"","url":"https://api.github.com/users/tomwilkie","html_url":"https://github.com/tomwilkie","followers_url":"https://api.github.com/users/tomwilkie/followers","following_url":"https://api.github.com/users/tomwilkie/following{/other_user}","gists_url":"https://api.github.com/users/tomwilkie/gists{/gist_id}","starred_url":"https://api.github.com/users/tomwilkie/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomwilkie/subscriptions","organizations_url":"https://api.github.com/users/tomwilkie/orgs","repos_url":"https://api.github.com/users/tomwilkie/repos","events_url":"https://api.github.com/users/tomwilkie/events{/privacy}","received_events_url":"https://api.github.com/users/tomwilkie/received_events","type":"User","site_admin":false},"assignees":[{"login":"tomwilkie","id":444037,"avatar_url":"https://avatars.githubusercontent.com/u/444037?v=3","gravatar_id":"","url":"https://api.github.com/users/tomwilkie","html_url":"https://github.com/tomwilkie","followers_url":"https://api.github.com/users/tomwilkie/followers","following_url":"https://api.github.com/users/tomwilkie/following{/other_user}","gists_url":"https://api.github.com/users/tomwilkie/gists{/gist_id}","starred_url":"https://api.github.com/users/tomwilkie/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomwilkie/subscriptions","organizations_url":"https://api.github.com/users/tomwilkie/orgs","repos_url":"https://api.github.com/users/tomwilkie/repos","events_url":"https://api.github.com/users/tomwilkie/events{/privacy}","received_events_url":"https://api.github.com/users/tomwilkie/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/weaveworks/cortex/pulls/181/commits","review_comments_url":"https://api.github.com/repos/weaveworks/cortex/pulls/181/comments","review_comment_url":"https://api.github.com/repos/weaveworks/cortex/pulls/comments{/number}","comments_url":"https://api.github.com/repos/weaveworks/cortex/issues/181/comments","statuses_url":"https://api.github.com/repos/weaveworks/cortex/statuses/030bdb83dcfde4e8a7180beb70d3708c20316b75","head":{"label":"weaveworks:weekly-tables","ref":"weekly-tables","sha":"030bdb83dcfde4e8a7180beb70d3708c20316b75","user":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars.githubusercontent.com/u/9976052?v=3","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"repo":{"id":67793333,"name":"cortex","full_name":"weaveworks/cortex","owner":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars.githubusercontent.com/u/9976052?v=3","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/weaveworks/cortex","description":"A multitenant, horizontally scalable Prometheus as a Service","fork":false,"url":"https://api.github.com/repos/weaveworks/cortex","forks_url":"https://api.github.com/repos/weaveworks/cortex/forks","keys_url":"https://api.github.com/repos/weaveworks/cortex/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weaveworks/cortex/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weaveworks/cortex/teams","hooks_url":"https://api.github.com/repos/weaveworks/cortex/hooks","issue_events_url":"https://api.github.com/repos/weaveworks/cortex/issues/events{/number}","events_url":"https://api.github.com/repos/weaveworks/cortex/events","assignees_url":"https://api.github.com/repos/weaveworks/cortex/assignees{/user}","branches_url":"https://api.github.com/repos/weaveworks/cortex/branches{/branch}","tags_url":"https://api.github.com/repos/weaveworks/cortex/tags","blobs_url":"https://api.github.com/repos/weaveworks/cortex/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weaveworks/cortex/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weaveworks/cortex/git/refs{/sha}","trees_url":"https://api.github.com/repos/weaveworks/cortex/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weaveworks/cortex/statuses/{sha}","languages_url":"https://api.github.com/repos/weaveworks/cortex/languages","stargazers_url":"https://api.github.com/repos/weaveworks/cortex/stargazers","contributors_url":"https://api.github.com/repos/weaveworks/cortex/contributors","subscribers_url":"https://api.github.com/repos/weaveworks/cortex/subscribers","subscription_url":"https://api.github.com/repos/weaveworks/cortex/subscription","commits_url":"https://api.github.com/repos/weaveworks/cortex/commits{/sha}","git_commits_url":"https://api.github.com/repos/weaveworks/cortex/git/commits{/sha}","comments_url":"https://api.github.com/repos/weaveworks/cortex/comments{/number}","issue_comment_url":"https://api.github.com/repos/weaveworks/cortex/issues/comments{/number}","contents_url":"https://api.github.com/repos/weaveworks/cortex/contents/{+path}","compare_url":"https://api.github.com/repos/weaveworks/cortex/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weaveworks/cortex/merges","archive_url":"https://api.github.com/repos/weaveworks/cortex/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weaveworks/cortex/downloads","issues_url":"https://api.github.com/repos/weaveworks/cortex/issues{/number}","pulls_url":"https://api.github.com/repos/weaveworks/cortex/pulls{/number}","milestones_url":"https://api.github.com/repos/weaveworks/cortex/milestones{/number}","notifications_url":"https://api.github.com/repos/weaveworks/cortex/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weaveworks/cortex/labels{/name}","releases_url":"https://api.github.com/repos/weaveworks/cortex/releases{/id}","deployments_url":"https://api.github.com/repos/weaveworks/cortex/deployments","created_at":"2016-09-09T11:23:12Z","updated_at":"2017-01-03T10:07:32Z","pushed_at":"2017-01-06T16:00:20Z","git_url":"git://github.com/weaveworks/cortex.git","ssh_url":"git@github.com:weaveworks/cortex.git","clone_url":"https://github.com/weaveworks/cortex.git","svn_url":"https://github.com/weaveworks/cortex","homepage":null,"size":6752,"stargazers_count":66,"watchers_count":66,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5,"mirror_url":null,"open_issues_count":30,"forks":5,"open_issues":30,"watchers":66,"default_branch":"master"}},"base":{"label":"weaveworks:master","ref":"master","sha":"05ef9d6d69982342e3d2dfad23bcce75e534505e","user":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars.githubusercontent.com/u/9976052?v=3","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"repo":{"id":67793333,"name":"cortex","full_name":"weaveworks/cortex","owner":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars.githubusercontent.com/u/9976052?v=3","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/weaveworks/cortex","description":"A multitenant, horizontally scalable Prometheus as a Service","fork":false,"url":"https://api.github.com/repos/weaveworks/cortex","forks_url":"https://api.github.com/repos/weaveworks/cortex/forks","keys_url":"https://api.github.com/repos/weaveworks/cortex/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weaveworks/cortex/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weaveworks/cortex/teams","hooks_url":"https://api.github.com/repos/weaveworks/cortex/hooks","issue_events_url":"https://api.github.com/repos/weaveworks/cortex/issues/events{/number}","events_url":"https://api.github.com/repos/weaveworks/cortex/events","assignees_url":"https://api.github.com/repos/weaveworks/cortex/assignees{/user}","branches_url":"https://api.github.com/repos/weaveworks/cortex/branches{/branch}","tags_url":"https://api.github.com/repos/weaveworks/cortex/tags","blobs_url":"https://api.github.com/repos/weaveworks/cortex/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weaveworks/cortex/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weaveworks/cortex/git/refs{/sha}","trees_url":"https://api.github.com/repos/weaveworks/cortex/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weaveworks/cortex/statuses/{sha}","languages_url":"https://api.github.com/repos/weaveworks/cortex/languages","stargazers_url":"https://api.github.com/repos/weaveworks/cortex/stargazers","contributors_url":"https://api.github.com/repos/weaveworks/cortex/contributors","subscribers_url":"https://api.github.com/repos/weaveworks/cortex/subscribers","subscription_url":"https://api.github.com/repos/weaveworks/cortex/subscription","commits_url":"https://api.github.com/repos/weaveworks/cortex/commits{/sha}","git_commits_url":"https://api.github.com/repos/weaveworks/cortex/git/commits{/sha}","comments_url":"https://api.github.com/repos/weaveworks/cortex/comments{/number}","issue_comment_url":"https://api.github.com/repos/weaveworks/cortex/issues/comments{/number}","contents_url":"https://api.github.com/repos/weaveworks/cortex/contents/{+path}","compare_url":"https://api.github.com/repos/weaveworks/cortex/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weaveworks/cortex/merges","archive_url":"https://api.github.com/repos/weaveworks/cortex/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weaveworks/cortex/downloads","issues_url":"https://api.github.com/repos/weaveworks/cortex/issues{/number}","pulls_url":"https://api.github.com/repos/weaveworks/cortex/pulls{/number}","milestones_url":"https://api.github.com/repos/weaveworks/cortex/milestones{/number}","notifications_url":"https://api.github.com/repos/weaveworks/cortex/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weaveworks/cortex/labels{/name}","releases_url":"https://api.github.com/repos/weaveworks/cortex/releases{/id}","deployments_url":"https://api.github.com/repos/weaveworks/cortex/deployments","created_at":"2016-09-09T11:23:12Z","updated_at":"2017-01-03T10:07:32Z","pushed_at":"2017-01-06T16:00:20Z","git_url":"git://github.com/weaveworks/cortex.git","ssh_url":"git@github.com:weaveworks/cortex.git","clone_url":"https://github.com/weaveworks/cortex.git","svn_url":"https://github.com/weaveworks/cortex","homepage":null,"size":6752,"stargazers_count":66,"watchers_count":66,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5,"mirror_url":null,"open_issues_count":30,"forks":5,"open_issues":30,"watchers":66,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/weaveworks/cortex/pulls/181"},"html":{"href":"https://github.com/weaveworks/cortex/pull/181"},"issue":{"href":"https://api.github.com/repos/weaveworks/cortex/issues/181"},"comments":{"href":"https://api.github.com/repos/weaveworks/cortex/issues/181/comments"},"review_comments":{"href":"https://api.github.com/repos/weaveworks/cortex/pulls/181/comments"},"review_comment":{"href":"https://api.github.com/repos/weaveworks/cortex/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/weaveworks/cortex/pulls/181/commits"},"statuses":{"href":"https://api.github.com/repos/weaveworks/cortex/statuses/030bdb83dcfde4e8a7180beb70d3708c20316b75"}}}}
{"actor":{"display_login":"jml"}}