{"action":"created","comment":{"url":"https://api.github.com/repos/twosigma/Cook/pulls/comments/229357344","pull_request_review_id":169839119,"id":229357344,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyOTM1NzM0NA==","diff_hunk":"@@ -19,122 +19,251 @@\n             [clj-http.client :as http]\n             [clj-time.core :as t]\n             [clj-time.coerce :as tc]\n-            [clj-time.periodic :as periodic]\n-            [clojure.core.async :as async]\n             [clojure.tools.logging :as log]\n-            [cook.util :refer [lazy-load-var PosNum PosInt NonNegInt]]\n+            [cook.util :refer [lazy-load-var NonNegInt PosNum PosInt]]\n             [cook.mesos.util :as util]\n-            [datomic.api :as d :refer (q)]\n-            [schema.core :as s]))\n+            [metrics.timers :as timers]\n+            [plumbing.core :as pc]\n+            [schema.core :as s])\n+  (:import (java.util UUID)))\n \n (def TimePeriodMs NonNegInt)\n \n+\n+;; Elements missing:\n+;;   1. Attributes (chip set, az, ..) -- have a combinatorial problem here\n+;;   2. Price\n+;;   3. Prob preemption in 10 minutes increments (or something like that)\n (def HostInfo\n+  \"Example:\n+   {\\\"count\\\" 4\n+    \\\"cpus\\\" 8\n+    \\\"instance-type\\\" \\\"basic\\\"\n+    \\\"mem\\\" 240000}\"\n   {:count NonNegInt\n-   :instance-type s/Str\n    :cpus PosNum\n+   :instance-type s/Str\n    :mem PosNum\n-   (s/optional-key :gpus) PosNum})\n-\n-(defprotocol HostFeed\n-  \"Protocol defining a service to get information on hosts that can be purchased.\"\n-  (get-available-host-info [this] \"Returns a list of host info maps conforming\n-                                   to HostInfo schema above\"))\n+   (s/optional-key :attributes) {s/Keyword s/Any}\n+   (s/optional-key :gpus) PosNum\n+   (s/optional-key :time-to-start) TimePeriodMs})\n \n (def Schedule\n-  \"Schedule returned by optimizer. Each TimePeriodMs is some milliseconds into the future the optimizer\n-   recommendation map applies.\n-   The optimizer recommendation map currently only has one key, :suggested-matches which has the matches\n-   recommended as a value. The matches recommended are structured as a map from the host type in question\n-   to a list of job UUIDs the optimizer recommends scheduling\n+  \"Schedule returned by optimizer.\n+   Each TimePeriodMs is some milliseconds into the future the optimizer recommendation map applies.\n+   The optimizer recommendation map currently only has one key,\n+   :suggested-matches which has the matches recommended as a value.\n+   The matches recommended are structured as a map from the host type in question to a list of job UUIDs\n+   the optimizer recommends scheduling.\n \n    Example schedule:\n-   {0 {:suggested-matches {{:count 10, :instance-type :mem-optimized, :cpus 10, :mem 200000}\n-                           [#uuid \\\"1db521d2-b1d5-41b4-9a93-a7e12abd940b\\\"]\n-                           {:count 100, :instance-type :cpu-optimized, :cpus 40, :mem 60000}\n-                           [#uuid \\\"5ac521d2-b1d5-41b4-9a93-a7e12abd4326\\\"]}}\n-    60000 {:suggested-matches {{:count 10, :instance-type :mem-optimized, :cpus 10, :mem 200000}\n-                               [#uuid \\\"724521d2-b1d5-41b4-9a93-a7e12abd940b\\\"]\n-                               {:count 100, :instance-type :cpu-optimized, :cpus 40, :mem 60000}\n-                               [#uuid \\\"adc521d2-b1d5-41b4-9a93-a7e12abd4326\\\"]}}}\"\n-  {TimePeriodMs {:suggested-matches {HostInfo [s/Uuid]}}})\n+   {0 {:suggested-matches [#uuid \\\"1db521d2-b1d5-41b4-9a93-a7e12abd940b\\\"]}\n+    60000 {:suggested-matches [#uuid \\\"724521d2-b1d5-41b4-9a93-a7e12abd940b\\\"]}}\"\n+  {TimePeriodMs {:suggested-matches [s/Uuid]}})\n \n \n (defprotocol Optimizer\n   \"Protocol defining a tool to produce a schedule to execute\"\n-  (produce-schedule [this queue running available host-infos]\n-                    \"Returns a schedule of what jobs to place on what machines\n-                     and what hosts to purchase at different time steps.\n-                     Conforms to the Schedule schema above\n-\n-                     Parameters:\n-                     queue -- Ordered list of jobs to run (see cook.mesos.schema job)\n-                     running -- Set of tasks running (see cook.mesos.schema instance)\n-                     available -- Set of offers outstanding\n-                     host-infos -- Host infos from HostFeed\n-\n-                     Returns:\n-                     A schedule: a map where the keys are milliseconds in the future\n-                     and  the values are recommendations to take at that point in the future\"))\n-\n-(defn create-dummy-host-feed\n-  \"Returns an instance of HostFeed which returns an empty list of hosts\"\n-  [_]\n-  (log/info \"Creating dummy host feed\")\n-  (reify HostFeed\n-    (get-available-host-info [this]\n-      [])))\n+  (produce-schedule\n+    [this queue running]\n+    \"Returns a schedule of what jobs to place on what machines\n+     and what hosts to purchase at different time steps.\n+     Conforms to the Schedule schema above\n+\n+     Parameters:\n+     queue -- Ordered list of jobs to run (see cook.mesos.schema job)\n+     running -- Set of tasks running (see cook.mesos.schema instance)\n+     host-infos -- Host infos from HostFeed\n+\n+     Returns:\n+     A schedule: a map where the keys are milliseconds in the future\n+     and  the values are recommendations to take at that point in the future\"))\n \n (defn create-dummy-optimizer\n   \"Returns an instance of Optimizer which returns an empty schedule\"\n   [_]\n   (log/info \"Creating dummy optimizer\")\n   (reify Optimizer\n-    (produce-schedule [this queue running available host-infos]\n-      {0 {:suggested-matches {}}})))\n+    (produce-schedule [_ _ _] {0 {:suggested-matches []}})))\n \n-(defn optimizer-cycle!\n+(defn instance-runtime\n+  \"Returns the instance runtime.\"\n+  [instance now]\n+  (let [start (:instance/start-time instance)\n+        end (:instance/end-time instance now)]\n+    (- (.getTime end)\n+       (.getTime start))))\n+\n+(defn calculate-expected-complete-time\n+  \"Returns the expected time remaining to job completion.\n+   Simple model for if run time exceeds expected runtime: the more it exceeds expected, the longer we expect it to take.\n+   TODO: get better model!\"\n+  [instance expected-duration now]\n+  (->> (instance-runtime instance now)\n+       (- expected-duration)\n+       Math/abs))\n+\n+(defn job->batch-uuid\n+  \"Retrieves a batch UUID for the provided job.\"\n+  [job]\n+  (or (-> job :group/_job :group/uuid)\n+      (:job/uuid job)))\n+\n+(defn waiting-job->optimizer-job\n+  \"Converts a waiting job to an optimizer job representation.\"\n+  [job default-runtime]\n+  (let [expected-duration (get job :job/expected-runtime default-runtime)\n+        resources (util/job-ent->resources job)\n+        batch-uuid (job->batch-uuid job)]\n+    (merge {:batch batch-uuid\n+            :count 1\n+            :dur expected-duration\n+            :expected_complete_time -1\n+            :running_host_group -1 ; TODO write function to determine this\n+            :state \"waiting\"\n+            :submit_time (-> job :job/submit-time .getTime)\n+            :user (:job/user job)\n+            :uuid (:job/uuid job)}\n+           resources)))\n+\n+(defn running-instance->optimizer-job\n+  \"Converts a running instance to an optimizer job representation.\"\n+  [instance default-runtime now host-name->host-group]\n+  (let [job (:job/_instance instance)\n+        expected-duration (get job :job/expected-runtime default-runtime)\n+        expected-complete-time (calculate-expected-complete-time instance expected-duration now)\n+        resources (util/job-ent->resources job)\n+        batch-uuid (job->batch-uuid job)]\n+    (merge {:batch batch-uuid\n+            :count 1\n+            :dur expected-duration\n+            :expected_complete_time expected-complete-time\n+            :running_host_group (-> instance :instance/hostname host-name->host-group)\n+            :state \"running\"\n+            :submit_time (-> job :job/submit-time .getTime)\n+            :user (:job/user job)\n+            :uuid (:job/uuid job)}\n+           resources)))\n+\n+(defn create-optimizer-server\n+  [{:keys [cpu-share default-runtime host-info max-waiting mem-share opt-params opt-server step-list]\n+    :or {opt-params {}}\n+    :as config}]\n+  {:pre [(integer? default-runtime)\n+         (pos? default-runtime)\n+         (s/validate HostInfo host-info)\n+         (integer? max-waiting)\n+         (pos? max-waiting)\n+         (seq step-list)]}\n+  (log/info \"Optimizer config\" config)\n+  (let [host-group 0\n+        host-name->host-group (constantly host-group)\n+        host-infos [host-info]\n+        user->ema-mem-usage (atom {})]\n+    (reify Optimizer\n+      (produce-schedule [_ queued-jobs running-tasks]\n+        (let [waiting-jobs (take max-waiting queued-jobs)]\n+          (log/info \"Optimizer inputs\" {:running-tasks (count running-tasks)\n+                                        :waiting-jobs (count waiting-jobs)})\n+          (if (-> waiting-jobs count pos?)\n+            (let [alpha 0.5\n+                  now (tc/to-date (t/now))\n+                  _ (log/info \"Converting waiting jobs to optimizer jobs\")\n+                  waiting-opt-jobs (timers/time!\n+                                     (timers/timer [\"cook-mesos\" \"scheduler\" \"optimizer\" \"waiting-jobs\"])\n+                                     (->> waiting-jobs\n+                                          (take max-waiting)\n+                                          (mapv #(waiting-job->optimizer-job % default-runtime))))\n+                  _ (log/info \"Converting running jobs to optimizer jobs\")\n+                  running-opt-jobs (timers/time!\n+                                     (timers/timer [\"cook-mesos\" \"scheduler\" \"optimizer\" \"running-jobs\"])\n+                                     (->> running-tasks\n+                                          (mapv #(running-instance->optimizer-job % default-runtime now host-name->host-group))))\n+                  _ (log/info \"Computing user memory usage\")\n+                  user->mem-usage (timers/time!\n+                                    (timers/timer [\"cook-mesos\" \"scheduler\" \"optimizer\" \"user->mem-usage\"])\n+                                    (->> running-opt-jobs\n+                                         (group-by :user)\n+                                         (pc/map-vals #(reduce + 0 (map :mem %)))\n+                                         (pc/map-vals #(* (- 1 alpha) %))))\n+                  _ (swap! user->ema-mem-usage\n+                           (fn [ema-usage]\n+                             (->> (pc/map-vals (partial * alpha) ema-usage)\n+                                  (merge-with + user->mem-usage))))\n+                  ;; Running must be before waiting here because optimizer determines batch order from job order\n+                  opt-jobs (concat running-opt-jobs waiting-opt-jobs)\n+                  ;; TODO mem-share should be computed per user.\n+                  user->ema-usage (pc/map-vals #(/ % mem-share) @user->ema-mem-usage)\n+                  _ (log/info \"Preparing request body\")\n+                  request-body (timers/time!\n+                                 (timers/timer [\"cook-mesos\" \"scheduler\" \"optimizer\" \"prepare-request\"])\n+                                 (cheshire/generate-string\n+                                   {\"cpu_share\" cpu-share\n+                                    \"host_groups\" host-infos\n+                                    \"mem_share\" mem-share\n+                                    \"now\" (.getTime now)\n+                                    \"opt_jobs\" opt-jobs\n+                                    \"opt_params\" opt-params\n+                                    \"seed\" (.getTime now)","path":"scheduler/src/cook/mesos/optimizer.clj","position":255,"original_position":240,"commit_id":"a853201537f86ebfb05c7d95925b7c0fd03dce3d","original_commit_id":"c643468da0284b4fe19993013e86483dceadc219","user":{"login":"DaoWen","id":1783674,"node_id":"MDQ6VXNlcjE3ODM2NzQ=","avatar_url":"https://avatars3.githubusercontent.com/u/1783674?v=4","gravatar_id":"","url":"https://api.github.com/users/DaoWen","html_url":"https://github.com/DaoWen","followers_url":"https://api.github.com/users/DaoWen/followers","following_url":"https://api.github.com/users/DaoWen/following{/other_user}","gists_url":"https://api.github.com/users/DaoWen/gists{/gist_id}","starred_url":"https://api.github.com/users/DaoWen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DaoWen/subscriptions","organizations_url":"https://api.github.com/users/DaoWen/orgs","repos_url":"https://api.github.com/users/DaoWen/repos","events_url":"https://api.github.com/users/DaoWen/events{/privacy}","received_events_url":"https://api.github.com/users/DaoWen/received_events","type":"User","site_admin":false},"body":"OK, recovering the seed from the logs works too.","created_at":"2018-10-30T15:31:03Z","updated_at":"2018-10-30T15:31:04Z","html_url":"https://github.com/twosigma/Cook/pull/992#discussion_r229357344","pull_request_url":"https://api.github.com/repos/twosigma/Cook/pulls/992","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/twosigma/Cook/pulls/comments/229357344"},"html":{"href":"https://github.com/twosigma/Cook/pull/992#discussion_r229357344"},"pull_request":{"href":"https://api.github.com/repos/twosigma/Cook/pulls/992"}},"in_reply_to_id":228033502},"pull_request":{"url":"https://api.github.com/repos/twosigma/Cook/pulls/992","id":222527744,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIyNTI3NzQ0","html_url":"https://github.com/twosigma/Cook/pull/992","diff_url":"https://github.com/twosigma/Cook/pull/992.diff","patch_url":"https://github.com/twosigma/Cook/pull/992.patch","issue_url":"https://api.github.com/repos/twosigma/Cook/issues/992","number":992,"state":"open","locked":false,"title":"[WIP] Optimizer server integration","user":{"login":"shamsimam","id":6611249,"node_id":"MDQ6VXNlcjY2MTEyNDk=","avatar_url":"https://avatars0.githubusercontent.com/u/6611249?v=4","gravatar_id":"","url":"https://api.github.com/users/shamsimam","html_url":"https://github.com/shamsimam","followers_url":"https://api.github.com/users/shamsimam/followers","following_url":"https://api.github.com/users/shamsimam/following{/other_user}","gists_url":"https://api.github.com/users/shamsimam/gists{/gist_id}","starred_url":"https://api.github.com/users/shamsimam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shamsimam/subscriptions","organizations_url":"https://api.github.com/users/shamsimam/orgs","repos_url":"https://api.github.com/users/shamsimam/repos","events_url":"https://api.github.com/users/shamsimam/events{/privacy}","received_events_url":"https://api.github.com/users/shamsimam/received_events","type":"User","site_admin":false},"body":"## Changes proposed in this PR\r\n\r\n- integrate optimizer output into the ranker\r\n- update the simulator to retrieve results from the optimizer server\r\n- include example code for the optimizer server and configuration\r\n\r\n## Why are we making these changes?\r\n\r\nAdds support for the group (batch) scheduling optimizer server. The server can act as a 'slow' brain offering hints to the ranker on how to rank pending jobs.\r\n\r\n","created_at":"2018-10-12T16:56:46Z","updated_at":"2018-10-30T15:31:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"745f95fc9572e5031464f0c66034eccd1c2046b5","assignee":{"login":"shamsimam","id":6611249,"node_id":"MDQ6VXNlcjY2MTEyNDk=","avatar_url":"https://avatars0.githubusercontent.com/u/6611249?v=4","gravatar_id":"","url":"https://api.github.com/users/shamsimam","html_url":"https://github.com/shamsimam","followers_url":"https://api.github.com/users/shamsimam/followers","following_url":"https://api.github.com/users/shamsimam/following{/other_user}","gists_url":"https://api.github.com/users/shamsimam/gists{/gist_id}","starred_url":"https://api.github.com/users/shamsimam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shamsimam/subscriptions","organizations_url":"https://api.github.com/users/shamsimam/orgs","repos_url":"https://api.github.com/users/shamsimam/repos","events_url":"https://api.github.com/users/shamsimam/events{/privacy}","received_events_url":"https://api.github.com/users/shamsimam/received_events","type":"User","site_admin":false},"assignees":[{"login":"shamsimam","id":6611249,"node_id":"MDQ6VXNlcjY2MTEyNDk=","avatar_url":"https://avatars0.githubusercontent.com/u/6611249?v=4","gravatar_id":"","url":"https://api.github.com/users/shamsimam","html_url":"https://github.com/shamsimam","followers_url":"https://api.github.com/users/shamsimam/followers","following_url":"https://api.github.com/users/shamsimam/following{/other_user}","gists_url":"https://api.github.com/users/shamsimam/gists{/gist_id}","starred_url":"https://api.github.com/users/shamsimam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shamsimam/subscriptions","organizations_url":"https://api.github.com/users/shamsimam/orgs","repos_url":"https://api.github.com/users/shamsimam/repos","events_url":"https://api.github.com/users/shamsimam/events{/privacy}","received_events_url":"https://api.github.com/users/shamsimam/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":758298439,"node_id":"MDU6TGFiZWw3NTgyOTg0Mzk=","url":"https://api.github.com/repos/twosigma/Cook/labels/wip","name":"wip","color":"000000","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/twosigma/Cook/pulls/992/commits","review_comments_url":"https://api.github.com/repos/twosigma/Cook/pulls/992/comments","review_comment_url":"https://api.github.com/repos/twosigma/Cook/pulls/comments{/number}","comments_url":"https://api.github.com/repos/twosigma/Cook/issues/992/comments","statuses_url":"https://api.github.com/repos/twosigma/Cook/statuses/a853201537f86ebfb05c7d95925b7c0fd03dce3d","head":{"label":"twosigma:optimizer-integration","ref":"optimizer-integration","sha":"a853201537f86ebfb05c7d95925b7c0fd03dce3d","user":{"login":"twosigma","id":4049181,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQwNDkxODE=","avatar_url":"https://avatars3.githubusercontent.com/u/4049181?v=4","gravatar_id":"","url":"https://api.github.com/users/twosigma","html_url":"https://github.com/twosigma","followers_url":"https://api.github.com/users/twosigma/followers","following_url":"https://api.github.com/users/twosigma/following{/other_user}","gists_url":"https://api.github.com/users/twosigma/gists{/gist_id}","starred_url":"https://api.github.com/users/twosigma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twosigma/subscriptions","organizations_url":"https://api.github.com/users/twosigma/orgs","repos_url":"https://api.github.com/users/twosigma/repos","events_url":"https://api.github.com/users/twosigma/events{/privacy}","received_events_url":"https://api.github.com/users/twosigma/received_events","type":"Organization","site_admin":false},"repo":{"id":42325066,"node_id":"MDEwOlJlcG9zaXRvcnk0MjMyNTA2Ng==","name":"Cook","full_name":"twosigma/Cook","private":false,"owner":{"login":"twosigma","id":4049181,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQwNDkxODE=","avatar_url":"https://avatars3.githubusercontent.com/u/4049181?v=4","gravatar_id":"","url":"https://api.github.com/users/twosigma","html_url":"https://github.com/twosigma","followers_url":"https://api.github.com/users/twosigma/followers","following_url":"https://api.github.com/users/twosigma/following{/other_user}","gists_url":"https://api.github.com/users/twosigma/gists{/gist_id}","starred_url":"https://api.github.com/users/twosigma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twosigma/subscriptions","organizations_url":"https://api.github.com/users/twosigma/orgs","repos_url":"https://api.github.com/users/twosigma/repos","events_url":"https://api.github.com/users/twosigma/events{/privacy}","received_events_url":"https://api.github.com/users/twosigma/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/twosigma/Cook","description":"Fair job scheduler on Mesos for batch workloads and Spark","fork":false,"url":"https://api.github.com/repos/twosigma/Cook","forks_url":"https://api.github.com/repos/twosigma/Cook/forks","keys_url":"https://api.github.com/repos/twosigma/Cook/keys{/key_id}","collaborators_url":"https://api.github.com/repos/twosigma/Cook/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/twosigma/Cook/teams","hooks_url":"https://api.github.com/repos/twosigma/Cook/hooks","issue_events_url":"https://api.github.com/repos/twosigma/Cook/issues/events{/number}","events_url":"https://api.github.com/repos/twosigma/Cook/events","assignees_url":"https://api.github.com/repos/twosigma/Cook/assignees{/user}","branches_url":"https://api.github.com/repos/twosigma/Cook/branches{/branch}","tags_url":"https://api.github.com/repos/twosigma/Cook/tags","blobs_url":"https://api.github.com/repos/twosigma/Cook/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/twosigma/Cook/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/twosigma/Cook/git/refs{/sha}","trees_url":"https://api.github.com/repos/twosigma/Cook/git/trees{/sha}","statuses_url":"https://api.github.com/repos/twosigma/Cook/statuses/{sha}","languages_url":"https://api.github.com/repos/twosigma/Cook/languages","stargazers_url":"https://api.github.com/repos/twosigma/Cook/stargazers","contributors_url":"https://api.github.com/repos/twosigma/Cook/contributors","subscribers_url":"https://api.github.com/repos/twosigma/Cook/subscribers","subscription_url":"https://api.github.com/repos/twosigma/Cook/subscription","commits_url":"https://api.github.com/repos/twosigma/Cook/commits{/sha}","git_commits_url":"https://api.github.com/repos/twosigma/Cook/git/commits{/sha}","comments_url":"https://api.github.com/repos/twosigma/Cook/comments{/number}","issue_comment_url":"https://api.github.com/repos/twosigma/Cook/issues/comments{/number}","contents_url":"https://api.github.com/repos/twosigma/Cook/contents/{+path}","compare_url":"https://api.github.com/repos/twosigma/Cook/compare/{base}...{head}","merges_url":"https://api.github.com/repos/twosigma/Cook/merges","archive_url":"https://api.github.com/repos/twosigma/Cook/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/twosigma/Cook/downloads","issues_url":"https://api.github.com/repos/twosigma/Cook/issues{/number}","pulls_url":"https://api.github.com/repos/twosigma/Cook/pulls{/number}","milestones_url":"https://api.github.com/repos/twosigma/Cook/milestones{/number}","notifications_url":"https://api.github.com/repos/twosigma/Cook/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/twosigma/Cook/labels{/name}","releases_url":"https://api.github.com/repos/twosigma/Cook/releases{/id}","deployments_url":"https://api.github.com/repos/twosigma/Cook/deployments","created_at":"2015-09-11T18:20:08Z","updated_at":"2018-10-30T03:24:45Z","pushed_at":"2018-10-29T22:23:54Z","git_url":"git://github.com/twosigma/Cook.git","ssh_url":"git@github.com:twosigma/Cook.git","clone_url":"https://github.com/twosigma/Cook.git","svn_url":"https://github.com/twosigma/Cook","homepage":"","size":43092,"stargazers_count":271,"watchers_count":271,"language":"Clojure","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":47,"mirror_url":null,"archived":false,"open_issues_count":96,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":47,"open_issues":96,"watchers":271,"default_branch":"master"}},"base":{"label":"twosigma:master","ref":"master","sha":"0d7def46e50dad5f172970a80faac9cd9b17f77b","user":{"login":"twosigma","id":4049181,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQwNDkxODE=","avatar_url":"https://avatars3.githubusercontent.com/u/4049181?v=4","gravatar_id":"","url":"https://api.github.com/users/twosigma","html_url":"https://github.com/twosigma","followers_url":"https://api.github.com/users/twosigma/followers","following_url":"https://api.github.com/users/twosigma/following{/other_user}","gists_url":"https://api.github.com/users/twosigma/gists{/gist_id}","starred_url":"https://api.github.com/users/twosigma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twosigma/subscriptions","organizations_url":"https://api.github.com/users/twosigma/orgs","repos_url":"https://api.github.com/users/twosigma/repos","events_url":"https://api.github.com/users/twosigma/events{/privacy}","received_events_url":"https://api.github.com/users/twosigma/received_events","type":"Organization","site_admin":false},"repo":{"id":42325066,"node_id":"MDEwOlJlcG9zaXRvcnk0MjMyNTA2Ng==","name":"Cook","full_name":"twosigma/Cook","private":false,"owner":{"login":"twosigma","id":4049181,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQwNDkxODE=","avatar_url":"https://avatars3.githubusercontent.com/u/4049181?v=4","gravatar_id":"","url":"https://api.github.com/users/twosigma","html_url":"https://github.com/twosigma","followers_url":"https://api.github.com/users/twosigma/followers","following_url":"https://api.github.com/users/twosigma/following{/other_user}","gists_url":"https://api.github.com/users/twosigma/gists{/gist_id}","starred_url":"https://api.github.com/users/twosigma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twosigma/subscriptions","organizations_url":"https://api.github.com/users/twosigma/orgs","repos_url":"https://api.github.com/users/twosigma/repos","events_url":"https://api.github.com/users/twosigma/events{/privacy}","received_events_url":"https://api.github.com/users/twosigma/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/twosigma/Cook","description":"Fair job scheduler on Mesos for batch workloads and Spark","fork":false,"url":"https://api.github.com/repos/twosigma/Cook","forks_url":"https://api.github.com/repos/twosigma/Cook/forks","keys_url":"https://api.github.com/repos/twosigma/Cook/keys{/key_id}","collaborators_url":"https://api.github.com/repos/twosigma/Cook/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/twosigma/Cook/teams","hooks_url":"https://api.github.com/repos/twosigma/Cook/hooks","issue_events_url":"https://api.github.com/repos/twosigma/Cook/issues/events{/number}","events_url":"https://api.github.com/repos/twosigma/Cook/events","assignees_url":"https://api.github.com/repos/twosigma/Cook/assignees{/user}","branches_url":"https://api.github.com/repos/twosigma/Cook/branches{/branch}","tags_url":"https://api.github.com/repos/twosigma/Cook/tags","blobs_url":"https://api.github.com/repos/twosigma/Cook/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/twosigma/Cook/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/twosigma/Cook/git/refs{/sha}","trees_url":"https://api.github.com/repos/twosigma/Cook/git/trees{/sha}","statuses_url":"https://api.github.com/repos/twosigma/Cook/statuses/{sha}","languages_url":"https://api.github.com/repos/twosigma/Cook/languages","stargazers_url":"https://api.github.com/repos/twosigma/Cook/stargazers","contributors_url":"https://api.github.com/repos/twosigma/Cook/contributors","subscribers_url":"https://api.github.com/repos/twosigma/Cook/subscribers","subscription_url":"https://api.github.com/repos/twosigma/Cook/subscription","commits_url":"https://api.github.com/repos/twosigma/Cook/commits{/sha}","git_commits_url":"https://api.github.com/repos/twosigma/Cook/git/commits{/sha}","comments_url":"https://api.github.com/repos/twosigma/Cook/comments{/number}","issue_comment_url":"https://api.github.com/repos/twosigma/Cook/issues/comments{/number}","contents_url":"https://api.github.com/repos/twosigma/Cook/contents/{+path}","compare_url":"https://api.github.com/repos/twosigma/Cook/compare/{base}...{head}","merges_url":"https://api.github.com/repos/twosigma/Cook/merges","archive_url":"https://api.github.com/repos/twosigma/Cook/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/twosigma/Cook/downloads","issues_url":"https://api.github.com/repos/twosigma/Cook/issues{/number}","pulls_url":"https://api.github.com/repos/twosigma/Cook/pulls{/number}","milestones_url":"https://api.github.com/repos/twosigma/Cook/milestones{/number}","notifications_url":"https://api.github.com/repos/twosigma/Cook/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/twosigma/Cook/labels{/name}","releases_url":"https://api.github.com/repos/twosigma/Cook/releases{/id}","deployments_url":"https://api.github.com/repos/twosigma/Cook/deployments","created_at":"2015-09-11T18:20:08Z","updated_at":"2018-10-30T03:24:45Z","pushed_at":"2018-10-29T22:23:54Z","git_url":"git://github.com/twosigma/Cook.git","ssh_url":"git@github.com:twosigma/Cook.git","clone_url":"https://github.com/twosigma/Cook.git","svn_url":"https://github.com/twosigma/Cook","homepage":"","size":43092,"stargazers_count":271,"watchers_count":271,"language":"Clojure","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":47,"mirror_url":null,"archived":false,"open_issues_count":96,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":47,"open_issues":96,"watchers":271,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/twosigma/Cook/pulls/992"},"html":{"href":"https://github.com/twosigma/Cook/pull/992"},"issue":{"href":"https://api.github.com/repos/twosigma/Cook/issues/992"},"comments":{"href":"https://api.github.com/repos/twosigma/Cook/issues/992/comments"},"review_comments":{"href":"https://api.github.com/repos/twosigma/Cook/pulls/992/comments"},"review_comment":{"href":"https://api.github.com/repos/twosigma/Cook/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/twosigma/Cook/pulls/992/commits"},"statuses":{"href":"https://api.github.com/repos/twosigma/Cook/statuses/a853201537f86ebfb05c7d95925b7c0fd03dce3d"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"DaoWen"}}