{"action":"created","comment":{"url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/comments/91395284","pull_request_review_id":11901139,"id":91395284,"diff_hunk":"@@ -0,0 +1,356 @@\n++++\n+author = [\"Chris Lu\"]\n+date = \"2016-12-19T12:11:25-08:00\"\n+series = [\"Advent 2016\"]\n+title = \"Gleam: Distributed Map Reduce for Golang\"\n+\n++++\n+\n+After developing [Glow](https://github.com/chrislusf/glow) last year,\n+I came to realize the two limitations of Go for distributed data processing.\n+\n+First, generics are needed. Of course, we can use reflection. But it is \n+noticeably slower, to the point that I do not want to show the performance\n+numbers. Second, dynamic remote code execution is also\n+needed if we want to dynamically adjust the execution plan. We could pre-build\n+all the execution DAGs first and choose one of them during run time. But it\n+is very limiting.\n+\n+As everyone else here, I enjoyed the beauty of Go. How to make it work for\n+big data?\n+\n+Then I found LuaJIT. It seems an ideal complement to Go. For many who do\n+not know LuaJIT, its performance is on the same level as C, Java, Go. Being\n+a scripting language, it perfectly resolves the two limitations above:\n+generics and dynamic remote code execution. Lua was created as a small and\n+embeddable language. Go should embrace it. (BTW, LuaJIT's FFI is really\n+easy to call an external C library function, much simpler than CGO.)\n+\n+Additionally, Unix Pipe tools, e.g., \"sort\", \"tr\", \"uniq\", etc, \n+are also gems for data proccesing. But being single threaded, they lack \n+the power to tackle big data. Go should be able to help to scale them up,\n+to distributedly run these tools.\n+\n+With [Gleam](https://github.com/chrislusf/gleam), we can combine pure Go, \n+LuaJIT, and Unix Pipes, all the 3 powerful weapons together.\n+\n+Let's understand Gleam architecutre of how it works. \n+Then I will cover 2 examples:\n+\n+1. Sort an 1GB file in both standalone and distributed modes, and distributed\n+Unix sort mode, with performance comparison.\n+2. Implement Joining of CSV files.\n+\n+\n+# Architecture\n+\n+Gleam code defines the execution data flow, via simple Map(), Reduce() \n+operations. By default, the flow is executed locally. \n+\n+For any Lua code, a separate LuaJIT process is started and data is streamed\n+through it.\n+\n+The flow can also be executed in a distributed cluster. The Gleam cluster\n+has (currently) one master and multiple agents. The master's only job is\n+to collect resource statuses from agents. And agents needs to report their \n+statuses to the master.\n+\n+The agents also manage intermediate data. The data can be streamed via memory,\n+or optionally go through disk for better scalability and robustness.\n+\n+When run in a distributed cluster, the Gleam code becomes a driver of \n+the whole flow's execution. It requests resources from the master, and then\n+contact the agents to schedule jobs on them.\n+\n+One feature I like for Gleam is that the master and agents are very efficient.\n+They took about 5~6MB memory and almost no resource usages when idle. So\n+you can install it anywhere, e.g., on or close to the source database  \n+so the data be processed locally.\n+\n+# Installation\n+\n+First, follow https://github.com/chrislusf/gleam/wiki/Installation\n+\n+You just need to install LuaJIT and add one modified MessagePack lua file\n+to the Lua library load path.\n+\n+For distributed mode, you need to setup the Gleam cluster. It's actually \n+super simple. Just need to run\n+```sh\n+// on the master machine\n+$ gleam master\n+// on the agent machines\n+$ gleam agent --port=45327 --master=\"localhost:45326\"\n+$ gleam agent --port=45328 --master=\"localhost:45326\"\n+...\n+```\n+See https://github.com/chrislusf/gleam/wiki/Gleam-Cluster-Setup\n+\n+# Example 1: Sorting 1GB file\n+\n+## Input Data\n+The data input is 1GB gensort generated text data file.\n+http://www.ordinal.com/gensort.html\n+\n+```sh\n+$ gensort -a 10737418 record_1Gb_input.txt\n+```\n+\n+Before coding anything, let's use Unix sort to see how long it takes.\n+On my Mac(2.8GHz Intel Core i7, 16GB DDR3, APPLE SSD SM0512G),\n+\n+```sh\n+$ time sort -k 1 ~/Desktop/record_1Gb_input.txt > result.txt\n+\n+real\t4m57.111s\n+user\t4m55.379s\n+sys\t0m1.309s\n+```\n+Peak Memory used: 1.46 GB, CPU is about 100%.\n+\n+## Gleam Standalone mode\n+\n+The complete source code is here. It has these steps:\n+\n+1. read the input txt file line by line\n+- extract the interger part as a string from each line (LuaJIT)\n+- hash the key and distribute data into 4 partitions\n+- sort the key, first locally, then merge then into one partition\n+- print out data to stdout.\n+\n+There could be better algorithms. For example, instead of randomly hash the\n+key to partitions, we can just distribute the lines by the first few bits \n+to 4 partitions, local sort, and we can have 4 already sorted partitions.\n+But it loses the generality of the sorting code.\n+\n+```go\n+package main\n+\n+import (\n+\t\"os\"\n+\n+\t\"github.com/chrislusf/gleam/flow\"\n+)\n+\n+func main() {\n+\tflow.New().TextFile(\n+\t  \"/Users/chris/Desktop/record_1Gb_input.txt\",\n+\t).Map(`\n+\t  function(line)\n+\t    return string.sub(line, 1, 10), string.sub(line, 13)\n+\t  end\n+\t`).Partition(4).Sort().Fprintf(os.Stdout, \"%s  %s\\n\").Run()\n+}\n+```\n+\n+For Lua newbies, you may notice how similar Lua is to Go. Lua is \n+a pretty simple language. If you feel Go is easy, \n+[Lua is simpler](http://tylerneylon.com/a/learn-lua/).\n+For Gleam, you do not need to understand the advanced parts, \n+such as table, metatable.\n+\n+\n+Result:\n+```sh\n+$ time ./gleam_sort > result1.txt\n+\n+real\t2m55.412s\n+user\t7m26.631s\n+sys\t1m3.288s\n+\n+```\n+Peak Memory used: 5.29 GB, CPU is about 425%.\n+\n+The code used more CPU and memory than basic Unix sort. The actual sorting\n+time is shorter, from 4m57s reduced to 2m55s, 59% of the baseline.\n+\n+## Gleam Distributed mode with Unix Sort\n+\n+Next example needs a Gleam cluster. See \n+https://github.com/chrislusf/gleam/wiki/Gleam-Cluster-Setup for instructions.\n+\n+This piece of code does the following:\n+\n+1. read the input txt file line by line\n+2. extract the interger part as a string from each line (LuaJIT)\n+3. hash the key and distribute data into 4 partitions\n+4. sort the key locally for each partition (Unix sort command)\n+5. merge the 4 sorted partitions into one partition\n+6. print out data to stdout.\n+\n+```go\n+package main\n+\n+import (\n+\t\"os\"\n+\n+\t\"github.com/chrislusf/gleam/distributed\"\n+\t\"github.com/chrislusf/gleam/flow\"\n+)\n+\n+func main() {\n+\tflow.New().TextFile(\n+\t  \"/Users/chris/Desktop/record_1Gb_input.txt\",\n+\t).Map(`\n+\t  function(line)\n+\t    return string.sub(line, 1, 10), string.sub(line, 13)\n+\t  end\n+\t`).Partition(4).Pipe(`\n+\t  sort -k 1\n+\t`).MergeSortedTo(1).Fprintf(os.Stdout, \"%s  %s\\n\").Run(distributed.Option())\n+}\n+```\n+\n+Result\n+```sh\n+$ time ./gleam_sort > result2.txt\n+2016/11/25 17:25:22   taskGroup:Map.0-ScatterPartitions.0 : ScatterPartitions (5 MB)\n+2016/11/25 17:25:22 localhost:45326 allocated 1 executors with 5 MB memory.\n+2016/11/25 17:25:22   taskGroup:CollectPartitions.0-Pipe.0 : CollectPartitions (3 MB)\n+2016/11/25 17:25:22 localhost:45326 allocated 1 executors with 3 MB memory.\n+2016/11/25 17:25:22   taskGroup:CollectPartitions.2-Pipe.2 : CollectPartitions (3 MB)\n+2016/11/25 17:25:22   taskGroup:CollectPartitions.3-Pipe.3 : CollectPartitions (3 MB)\n+2016/11/25 17:25:22   taskGroup:CollectPartitions.1-Pipe.1 : CollectPartitions (3 MB)\n+2016/11/25 17:25:22 localhost:45326 allocated 3 executors with 9 MB memory.\n+2016/11/25 17:25:22   taskGroup:MergeSortedTo.0 : MergeSortedTo (5 MB)\n+2016/11/25 17:25:22 localhost:45326 allocated 1 executors with 5 MB memory.\n+\n+real\t2m54.228s\n+user\t0m17.761s\n+sys\t0m6.534s\n+```\n+\n+There are 4 separated \"sort\" processes running, each peaked at 100% CPU and 50MB.\n+The total time is about the same as the standalone Gleam sort code.\n+\n+The logs are just scheduling information, showing the tasks to do,\n+the amount of memory needed, the resources allocated, etc. Next example will\n+show a way to adjust the required memory.\n+\n+The data flowing between each dataset are usually in MessagePack format.\n+But the data flowing to and from Pipe() are tab-separated lines, \n+when each line has multiple fields. So there is a small amount of time \n+for the extra convertion.\n+\n+As you can see, as long as any program can work as Unix Pipes, Gleam\n+can make them work distributedly.\n+\n+## Gleam Distributed mode with Gleam Sort\n+\n+This piece of code takes these steps:\n+\n+1. read the input txt file line by line\n+2. extract the interger part as a string from each line (LuaJIT)\n+3. hash the key and distribute data into 4 partitions\n+4. sort the key locally for each partition, then merge into one partition\n+5. print out data to stdout.\n+\n+I added an optional hint to the flow, ```Hint(flow.TotalSize(1024))```, to\n+indicate the data size is 1024MB. When actually scheduling tasks on agents,\n+the driver program will ask Gleam master for corresponding resources, and \n+only agents with enough resources can get the job. So with this hint, \n+Gleam automatically allocates the memory.\n+\n+```go\n+package main\n+\n+import (\n+\t\"os\"\n+\n+\t\"github.com/chrislusf/gleam/distributed\"\n+\t\"github.com/chrislusf/gleam/flow\"\n+)\n+\n+func main() {\n+\tflow.New().TextFile(\n+\t  \"/Users/chris/Desktop/record_1Gb_input.txt\",\n+\t).Hint(flow.TotalSize(1024)).Map(`\n+\t  function(line)\n+\t    return string.sub(line, 1, 10), string.sub(line, 13)\n+\t  end\n+\t`).Partition(4).Sort().Fprintf(os.Stdout, \"%s  %s\\n\").Run(distributed.Option())\n+}\n+```\n+Result:\n+```sh\n+$ time ./gleam_sort > result3.txt\n+2016/11/25 17:54:42   taskGroup:Map.0-ScatterPartitions.0 : ScatterPartitions (5 MB)\n+2016/11/25 17:54:42 localhost:45326 allocated 1 executors with 5 MB memory.\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.0-LocalSort.0 : CollectPartitions (3 MB)\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.0-LocalSort.0 : LocalSort (768 MB)\n+2016/11/25 17:54:42 localhost:45326 allocated 1 executors with 771 MB memory.\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.2-LocalSort.2 : CollectPartitions (3 MB)\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.2-LocalSort.2 : LocalSort (768 MB)\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.1-LocalSort.1 : CollectPartitions (3 MB)\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.1-LocalSort.1 : LocalSort (768 MB)\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.3-LocalSort.3 : CollectPartitions (3 MB)\n+2016/11/25 17:54:42   taskGroup:CollectPartitions.3-LocalSort.3 : LocalSort (768 MB)\n+2016/11/25 17:54:42 localhost:45326 allocated 3 executors with 2313 MB memory.\n+2016/11/25 17:54:42   taskGroup:MergeSortedTo.0 : MergeSortedTo (5 MB)\n+2016/11/25 17:54:42 localhost:45326 allocated 1 executors with 5 MB memory.\n+\n+real\t1m55.188s\n+user\t1m6.554s\n+sys\t0m18.362s\n+\n+```\n+\n+Each of the 4 sorting executors peaked at about 868 MB, at 100% CPU.\n+\n+The code used more CPU and memory than basic Unix sort. Instead of on-disk\n+merge sort, the Gleam sort is just all in-memory Timsort.\n+The actual sorting time is reduced from 4m57s to 1m55s, 39% of the baseline.\n+\n+# Example 2: Joining CSV files\n+Data joining is another common use case for data processing. Here is a simple\n+example to illustrate how it works.\n+\n+Assume there are file \"a.csv\" has fields \"a1, a2, a3, a4, a5\",\n+and file \"b.csv\" has fields \"b1, b2, b3\". \n+\n+We want to join the rows where a1 = b2. \n+\n+And the output format should be \"a1, a4, b3\".\n+\n+```go\n+package main\n+\n+import (\n+    \"os\"\n+\n+    . \"github.com/chrislusf/gleam/flow\"\n+    \"github.com/chrislusf/gleam/plugins/csv\"\n+)\n+\n+func main() {\n+\n+    f := New()\n+    a := f.ReadFile(csv.New(\"a.csv\")).Select(Field(1,4)) // a1, a4\n+    b := f.ReadFile(csv.New(\"b.csv\")).Select(Field(2,3)) // b2, b3\n+\n+    a.Join(b).Fprintf(os.Stdout, \"%s,%s,%s\\n\").Run()  // a1, a4, b3\n+\n+}\n+\n+```\n+In the example, the csv files are on local disk. This is the simplest form. \n+The csv files can also be read from HDFS or S3.","path":"content/advent-2016/gleam-distributed-map-reduce-for-golang.md","position":337,"original_position":337,"commit_id":"e33056bd6c74c4eb1e0fba94dd587458ab59f7c8","original_commit_id":"e33056bd6c74c4eb1e0fba94dd587458ab59f7c8","user":{"login":"bradleyfalzon","id":1834577,"avatar_url":"https://avatars.githubusercontent.com/u/1834577?v=3","gravatar_id":"","url":"https://api.github.com/users/bradleyfalzon","html_url":"https://github.com/bradleyfalzon","followers_url":"https://api.github.com/users/bradleyfalzon/followers","following_url":"https://api.github.com/users/bradleyfalzon/following{/other_user}","gists_url":"https://api.github.com/users/bradleyfalzon/gists{/gist_id}","starred_url":"https://api.github.com/users/bradleyfalzon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bradleyfalzon/subscriptions","organizations_url":"https://api.github.com/users/bradleyfalzon/orgs","repos_url":"https://api.github.com/users/bradleyfalzon/repos","events_url":"https://api.github.com/users/bradleyfalzon/events{/privacy}","received_events_url":"https://api.github.com/users/bradleyfalzon/received_events","type":"User","site_admin":false},"body":"nit: I think `csv` should be `CSV`, this keeps consistent with `HDFS` and the next paragraph's use of `CSV`","created_at":"2016-12-07T21:48:22Z","updated_at":"2016-12-07T21:53:03Z","html_url":"https://github.com/gopheracademy/gopheracademy-web/pull/205#discussion_r91395284","pull_request_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205","_links":{"self":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/comments/91395284"},"html":{"href":"https://github.com/gopheracademy/gopheracademy-web/pull/205#discussion_r91395284"},"pull_request":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205"}}},"pull_request":{"url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205","id":96996848,"html_url":"https://github.com/gopheracademy/gopheracademy-web/pull/205","diff_url":"https://github.com/gopheracademy/gopheracademy-web/pull/205.diff","patch_url":"https://github.com/gopheracademy/gopheracademy-web/pull/205.patch","issue_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues/205","number":205,"state":"open","locked":false,"title":"adding Gleam","user":{"login":"chrislusf","id":1543151,"avatar_url":"https://avatars.githubusercontent.com/u/1543151?v=3","gravatar_id":"","url":"https://api.github.com/users/chrislusf","html_url":"https://github.com/chrislusf","followers_url":"https://api.github.com/users/chrislusf/followers","following_url":"https://api.github.com/users/chrislusf/following{/other_user}","gists_url":"https://api.github.com/users/chrislusf/gists{/gist_id}","starred_url":"https://api.github.com/users/chrislusf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrislusf/subscriptions","organizations_url":"https://api.github.com/users/chrislusf/orgs","repos_url":"https://api.github.com/users/chrislusf/repos","events_url":"https://api.github.com/users/chrislusf/events{/privacy}","received_events_url":"https://api.github.com/users/chrislusf/received_events","type":"User","site_admin":false},"body":"first draft","created_at":"2016-12-07T20:04:04Z","updated_at":"2016-12-07T21:53:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bf64d1976f7f081757bb2f48c0d950d129f08439","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205/commits","review_comments_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205/comments","review_comment_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues/205/comments","statuses_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/statuses/e33056bd6c74c4eb1e0fba94dd587458ab59f7c8","head":{"label":"chrislusf:master","ref":"master","sha":"e33056bd6c74c4eb1e0fba94dd587458ab59f7c8","user":{"login":"chrislusf","id":1543151,"avatar_url":"https://avatars.githubusercontent.com/u/1543151?v=3","gravatar_id":"","url":"https://api.github.com/users/chrislusf","html_url":"https://github.com/chrislusf","followers_url":"https://api.github.com/users/chrislusf/followers","following_url":"https://api.github.com/users/chrislusf/following{/other_user}","gists_url":"https://api.github.com/users/chrislusf/gists{/gist_id}","starred_url":"https://api.github.com/users/chrislusf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrislusf/subscriptions","organizations_url":"https://api.github.com/users/chrislusf/orgs","repos_url":"https://api.github.com/users/chrislusf/repos","events_url":"https://api.github.com/users/chrislusf/events{/privacy}","received_events_url":"https://api.github.com/users/chrislusf/received_events","type":"User","site_admin":false},"repo":{"id":75870954,"name":"gopheracademy-web","full_name":"chrislusf/gopheracademy-web","owner":{"login":"chrislusf","id":1543151,"avatar_url":"https://avatars.githubusercontent.com/u/1543151?v=3","gravatar_id":"","url":"https://api.github.com/users/chrislusf","html_url":"https://github.com/chrislusf","followers_url":"https://api.github.com/users/chrislusf/followers","following_url":"https://api.github.com/users/chrislusf/following{/other_user}","gists_url":"https://api.github.com/users/chrislusf/gists{/gist_id}","starred_url":"https://api.github.com/users/chrislusf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrislusf/subscriptions","organizations_url":"https://api.github.com/users/chrislusf/orgs","repos_url":"https://api.github.com/users/chrislusf/repos","events_url":"https://api.github.com/users/chrislusf/events{/privacy}","received_events_url":"https://api.github.com/users/chrislusf/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/chrislusf/gopheracademy-web","description":"Public Website for blog.GopherAcademy.com","fork":true,"url":"https://api.github.com/repos/chrislusf/gopheracademy-web","forks_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/forks","keys_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/keys{/key_id}","collaborators_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/teams","hooks_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/hooks","issue_events_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/issues/events{/number}","events_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/events","assignees_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/assignees{/user}","branches_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/branches{/branch}","tags_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/tags","blobs_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/git/refs{/sha}","trees_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/git/trees{/sha}","statuses_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/statuses/{sha}","languages_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/languages","stargazers_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/stargazers","contributors_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/contributors","subscribers_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/subscribers","subscription_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/subscription","commits_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/commits{/sha}","git_commits_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/git/commits{/sha}","comments_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/comments{/number}","issue_comment_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/issues/comments{/number}","contents_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/contents/{+path}","compare_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/compare/{base}...{head}","merges_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/merges","archive_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/downloads","issues_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/issues{/number}","pulls_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/pulls{/number}","milestones_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/milestones{/number}","notifications_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/labels{/name}","releases_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/releases{/id}","deployments_url":"https://api.github.com/repos/chrislusf/gopheracademy-web/deployments","created_at":"2016-12-07T19:59:42Z","updated_at":"2016-12-07T19:59:46Z","pushed_at":"2016-12-07T20:03:31Z","git_url":"git://github.com/chrislusf/gopheracademy-web.git","ssh_url":"git@github.com:chrislusf/gopheracademy-web.git","clone_url":"https://github.com/chrislusf/gopheracademy-web.git","svn_url":"https://github.com/chrislusf/gopheracademy-web","homepage":"http://blog.gopheracademy.com","size":13665,"stargazers_count":0,"watchers_count":0,"language":"CSS","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":"gopheracademy:master","ref":"master","sha":"2cbd4e2a984d9461c4a970ad28105e74a17f58cc","user":{"login":"gopheracademy","id":7428627,"avatar_url":"https://avatars.githubusercontent.com/u/7428627?v=3","gravatar_id":"","url":"https://api.github.com/users/gopheracademy","html_url":"https://github.com/gopheracademy","followers_url":"https://api.github.com/users/gopheracademy/followers","following_url":"https://api.github.com/users/gopheracademy/following{/other_user}","gists_url":"https://api.github.com/users/gopheracademy/gists{/gist_id}","starred_url":"https://api.github.com/users/gopheracademy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gopheracademy/subscriptions","organizations_url":"https://api.github.com/users/gopheracademy/orgs","repos_url":"https://api.github.com/users/gopheracademy/repos","events_url":"https://api.github.com/users/gopheracademy/events{/privacy}","received_events_url":"https://api.github.com/users/gopheracademy/received_events","type":"Organization","site_admin":false},"repo":{"id":23665322,"name":"gopheracademy-web","full_name":"gopheracademy/gopheracademy-web","owner":{"login":"gopheracademy","id":7428627,"avatar_url":"https://avatars.githubusercontent.com/u/7428627?v=3","gravatar_id":"","url":"https://api.github.com/users/gopheracademy","html_url":"https://github.com/gopheracademy","followers_url":"https://api.github.com/users/gopheracademy/followers","following_url":"https://api.github.com/users/gopheracademy/following{/other_user}","gists_url":"https://api.github.com/users/gopheracademy/gists{/gist_id}","starred_url":"https://api.github.com/users/gopheracademy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gopheracademy/subscriptions","organizations_url":"https://api.github.com/users/gopheracademy/orgs","repos_url":"https://api.github.com/users/gopheracademy/repos","events_url":"https://api.github.com/users/gopheracademy/events{/privacy}","received_events_url":"https://api.github.com/users/gopheracademy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/gopheracademy/gopheracademy-web","description":"Public Website for blog.GopherAcademy.com","fork":false,"url":"https://api.github.com/repos/gopheracademy/gopheracademy-web","forks_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/forks","keys_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/teams","hooks_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/hooks","issue_events_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues/events{/number}","events_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/events","assignees_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/assignees{/user}","branches_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/branches{/branch}","tags_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/tags","blobs_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/git/refs{/sha}","trees_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/statuses/{sha}","languages_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/languages","stargazers_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/stargazers","contributors_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/contributors","subscribers_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/subscribers","subscription_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/subscription","commits_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/commits{/sha}","git_commits_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/git/commits{/sha}","comments_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/comments{/number}","issue_comment_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues/comments{/number}","contents_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/contents/{+path}","compare_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/merges","archive_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/downloads","issues_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues{/number}","pulls_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls{/number}","milestones_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/milestones{/number}","notifications_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/labels{/name}","releases_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/releases{/id}","deployments_url":"https://api.github.com/repos/gopheracademy/gopheracademy-web/deployments","created_at":"2014-09-04T14:18:26Z","updated_at":"2016-12-06T07:10:22Z","pushed_at":"2016-12-07T20:04:04Z","git_url":"git://github.com/gopheracademy/gopheracademy-web.git","ssh_url":"git@github.com:gopheracademy/gopheracademy-web.git","clone_url":"https://github.com/gopheracademy/gopheracademy-web.git","svn_url":"https://github.com/gopheracademy/gopheracademy-web","homepage":"http://blog.gopheracademy.com","size":13805,"stargazers_count":89,"watchers_count":89,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":100,"mirror_url":null,"open_issues_count":10,"forks":100,"open_issues":10,"watchers":89,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205"},"html":{"href":"https://github.com/gopheracademy/gopheracademy-web/pull/205"},"issue":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues/205"},"comments":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/issues/205/comments"},"review_comments":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205/comments"},"review_comment":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/pulls/205/commits"},"statuses":{"href":"https://api.github.com/repos/gopheracademy/gopheracademy-web/statuses/e33056bd6c74c4eb1e0fba94dd587458ab59f7c8"}}}}
{"actor":{"display_login":"bradleyfalzon"}}