{"action":"created","comment":{"url":"https://api.github.com/repos/twitter/rpc-perf/pulls/comments/58396316","id":58396316,"diff_hunk":"@@ -68,160 +72,206 @@ impl Default for BenchmarkConfig {\n     }\n }\n \n-pub fn load_config(path: String) -> Result<BenchmarkConfig, &'static str> {\n+pub fn load_config(path: &str) -> Result<BenchmarkConfig, String> {\n     let mut f = File::open(&path).unwrap();\n \n     let mut s = String::new();\n     f.read_to_string(&mut s).unwrap();\n+    load_config_str(path, &s)\n+}\n \n-    let mut p = Parser::new(&s);\n+pub fn load_config_str(config_name: &str, config: &str) -> Result<BenchmarkConfig, String> {\n+    let mut p = Parser::new(config);\n \n     match p.parse() {\n-        Some(toml) => {\n+        Some(table) => {\n             debug!(\"toml parsed successfully. creating config\");\n \n             let mut config: BenchmarkConfig = Default::default();\n-            let table = Table(toml);\n \n-            if let Some(general) = table.lookup(\"general\") {\n-                if let Some(connections) = general.lookup(\"connections\")\n+            if let Some(&Table(ref general)) = table.get(\"general\") {\n+                if let Some(connections) = general.get(\"connections\")\n                                                   .and_then(|k| k.as_integer()) {\n                     config.connections = connections as usize;\n                 };\n-                if let Some(threads) = general.lookup(\"threads\").and_then(|k| k.as_integer()) {\n+                if let Some(threads) = general.get(\"threads\").and_then(|k| k.as_integer()) {\n                     config.threads = threads as usize;\n                 }\n-                if let Some(duration) = general.lookup(\"duration\")\n+                if let Some(duration) = general.get(\"duration\")\n                                                .and_then(|k| k.as_integer()) {\n                     config.duration = duration as usize;\n                 }\n-                if let Some(windows) = general.lookup(\"windows\").and_then(|k| k.as_integer()) {\n+                if let Some(windows) = general.get(\"windows\").and_then(|k| k.as_integer()) {\n                     config.windows = windows as usize;\n                 }\n-                if let Some(protocol) = general.lookup(\"protocol\").and_then(|k| k.as_str()) {\n+                if let Some(protocol) = general.get(\"protocol\").and_then(|k| k.as_str()) {\n                     config.protocol = protocol.to_owned();\n                 }\n-                if let Some(tcp_nodelay) = general.lookup(\"tcp-nodelay\")\n+                if let Some(tcp_nodelay) = general.get(\"tcp-nodelay\")\n                                                   .and_then(|k| k.as_bool()) {\n                     config.tcp_nodelay = tcp_nodelay;\n                 }\n-                if let Some(ipv4) = general.lookup(\"ipv4\").and_then(|k| k.as_bool()) {\n+                if let Some(ipv4) = general.get(\"ipv4\").and_then(|k| k.as_bool()) {\n                     config.ipv4 = ipv4;\n                 }\n-                if let Some(ipv6) = general.lookup(\"ipv6\").and_then(|k| k.as_bool()) {\n+                if let Some(ipv6) = general.get(\"ipv6\").and_then(|k| k.as_bool()) {\n                     config.ipv6 = ipv6;\n                 }\n             }\n \n-            let mut i = 0;\n-            loop {\n-                let key = format!(\"workload.{}\", i);\n-                match table.lookup(&key) {\n-                    Some(workload) => {\n-                        debug!(\"workload: {} defined\", i);\n-                        let mut w: BenchmarkWorkload = Default::default();\n-                        if let Some(method) = workload.lookup(\"method\").and_then(|k| k.as_str()) {\n-                            w.method = method.to_owned();\n-                        }\n-                        if let Some(rate) = workload.lookup(\"rate\").and_then(|k| k.as_integer()) {\n-                            w.rate = rate as usize;\n+            match table.get(\"workload\") {\n+                None => return Err(\"no workload section\".to_owned()),\n+                Some(&Array(ref workloads)) => {\n+                    for (i, workload) in workloads.iter().enumerate() {\n+                        if let &Table(ref workload) = workload {\n+                            let w = try!(extract_workload(i, workload));\n+                            config.workloads.push(w);\n+                        } else {\n+                            return Err(\"malformed config: workload must be a struct\".to_owned());\n                         }\n-                        let mut j = 0;\n-                        loop {\n-                            let param_key = format!(\"parameter.{}\", j);\n-                            match workload.lookup(&param_key) {\n-                                Some(parameter) => {\n-                                    let mut p = Parameter::default();\n-                                    p.id = match parameter.lookup(\"id\")\n-                                                          .and_then(|k| k.as_integer()) {\n-                                        Some(s) => Some(s as i16),\n-                                        None => None,\n-                                    };\n-\n-                                    p.ptype = match parameter.lookup(\"type\")\n-                                                             .and_then(|k| k.as_str()) {\n-                                        Some(\"stop\") => Type::Stop,\n-                                        Some(\"void\") => Type::Void,\n-                                        Some(\"bool\") => Type::Bool,\n-                                        Some(\"byte\") => Type::Byte,\n-                                        Some(\"double\") => Type::Double,\n-                                        Some(\"i16\") => Type::Int16,\n-                                        Some(\"i32\") => Type::Int32,\n-                                        Some(\"i64\") => Type::Int64,\n-                                        Some(\"string\") => Type::String,\n-                                        Some(\"struct\") => Type::Struct,\n-                                        Some(\"map\") => Type::Map,\n-                                        Some(\"set\") => Type::Set,\n-                                        Some(\"list\") => {\n-                                            Type::List(parameter.lookup(\"contains\")\n-                                                                .and_then(|k| k.as_str())\n-                                                                .unwrap()\n-                                                                .to_owned())\n-                                        }\n-                                        Some(_) => Type::None,\n-                                        None => Type::None,\n-                                    };\n-                                    let style = match parameter.lookup(\"style\")\n-                                                               .and_then(|k| k.as_str()) {\n-                                        Some(s) => s,\n-                                        None => \"static\",\n-                                    };\n-                                    p.seed = match parameter.lookup(\"seed\")\n-                                                            .and_then(|k| k.as_integer()) {\n-                                        Some(s) => s as usize,\n-                                        None => i,\n-                                    };\n-                                    p.size = match parameter.lookup(\"size\")\n-                                                            .and_then(|k| k.as_integer()) {\n-                                        Some(s) => s as usize,\n-                                        None => 1,\n-                                    };\n-                                    p.regenerate = match parameter.lookup(\"regenerate\")\n-                                                                  .and_then(|k| k.as_bool()) {\n-                                        Some(s) => s,\n-                                        None => false,\n-                                    };\n-                                    p.style = match style {\n-                                        \"random\" => Style::Random,\n-                                        \"static\" => Style::Static,\n-                                        _ => {\n-                                            panic!(\"bad parameter style: {}\", style);\n-                                        }\n-                                    };\n-                                    w.parameters.push(p);\n-                                    j += 1;\n-                                }\n-                                None => {\n-                                    break;\n-                                }\n-                            }\n-                        }\n-                        config.workloads.push(w);\n-                    }\n-                    None => {\n-                        break;\n                     }\n                 }\n-                i += 1;\n+                Some(_) => return Err(\"malformed config: workloads must be an array\".to_owned()),\n             }\n-            if i < 1 {\n-                return Err(\"no workload section\");\n-            }\n-            return Ok(config);\n+\n+            Ok(config)\n         }\n         None => {\n             for err in &p.errors {\n                 let (loline, locol) = p.to_linecol(err.lo);\n                 let (hiline, hicol) = p.to_linecol(err.hi);\n                 println!(\"{}:{}:{}-{}:{} error: {}\",\n-                         path,\n+                         config_name,\n                          loline,\n                          locol,\n                          hiline,\n                          hicol,\n                          err.desc);\n             }\n+            Err(\"failed to load config\".to_owned())\n+        }\n+    }\n+}\n+\n+fn extract_workload(i: usize, workload: &BTreeMap<String,Value>) -> CResult<BenchmarkWorkload> {\n+    let mut w: BenchmarkWorkload = Default::default();\n+    if let Some(method) = workload.get(\"method\").and_then(|k| k.as_str()) {\n+        w.method = method.to_owned();\n+    }\n+    if let Some(rate) = workload.get(\"rate\").and_then(|k| k.as_integer()) {\n+        w.rate = rate as usize;\n+    }\n+\n+    match workload.get(\"parameter\") {\n+        Some(&Array(ref params)) => {\n+            for param in params {\n+                match param {\n+                    &Table(ref parameter) => {\n+                        let p = try!(extract_parameters(i, parameter));\n+                        w.parameters.push(p);\n+                    }\n+                    _ => {\n+                        return Err(\"malformed config: a parameter must be a struct\".to_owned());\n+                    }\n+                }\n+            }\n+\n         }\n+        Some(_) => return Err(\"malformed config: 'parameter' must be an array\".to_owned()),\n+        None => {}\n     }\n-    Err(\"failed to load config\")\n+    Ok(w)\n+}\n+\n+fn extract_parameters(i: usize, parameter: &BTreeMap<String,Value>) -> CResult<Parameter> {\n+\n+    let mut p = Parameter::default();\n+    p.id = match parameter.get(\"id\")\n+                          .and_then(|k| k.as_integer()) {\n+        Some(s) => Some(s as i16),\n+        None => None,\n+    };\n+\n+    p.ptype = match parameter.get(\"type\")\n+                             .and_then(|k| k.as_str()) {\n+        Some(\"stop\") => Type::Stop,\n+        Some(\"void\") => Type::Void,\n+        Some(\"bool\") => Type::Bool,\n+        Some(\"byte\") => Type::Byte,\n+        Some(\"double\") => Type::Double,\n+        Some(\"i16\") => Type::Int16,\n+        Some(\"i32\") => Type::Int32,\n+        Some(\"i64\") => Type::Int64,\n+        Some(\"string\") => Type::String,\n+        Some(\"struct\") => Type::Struct,\n+        Some(\"map\") => Type::Map,\n+        Some(\"set\") => Type::Set,\n+        Some(\"list\") => {\n+            Type::List(parameter.get(\"contains\")\n+                                .and_then(|k| k.as_str())\n+                                .unwrap()\n+                                .to_owned())\n+        }\n+        Some(_) => Type::None,\n+        None => Type::None,\n+    };\n+\n+    p.style = match parameter.get(\"style\")\n+                               .and_then(|k| k.as_str()) {\n+        Some(\"random\") => Style::Random,\n+        Some(\"static\") => Style::Static,\n+        None => Style::Static,\n+        Some(other) => return Err(format!(\"bad parameter style: {}\", other)),\n+    };\n+\n+    p.seed = match parameter.get(\"seed\")\n+                            .and_then(|k| k.as_integer()) {\n+        Some(s) => s as usize,\n+        None => i,\n+    };\n+    p.size = match parameter.get(\"size\")\n+                            .and_then(|k| k.as_integer()) {\n+        Some(s) => s as usize,\n+        None => 1,\n+    };\n+    p.regenerate = match parameter.get(\"regenerate\")\n+                                  .and_then(|k| k.as_bool()) {\n+        Some(s) => s,\n+        None => false,\n+    };\n+\n+    Ok(p)\n+}\n+\n+#[test]\n+fn test_load_config() {\n+    let config_str = include_str!(\"../configs/thrift_calc.toml\");\n+    let workload = load_config_str(\"thrift_calc.toml\", config_str).unwrap();\n+\n+    assert_eq!(workload.protocol, \"thrift\");\n+    assert_eq!(workload.workloads.len(), 3);\n+\n+    let w0 = &workload.workloads[0];\n+    // Check the first workload\n+    assert_eq!(w0.method, \"ping\");\n+    assert_eq!(w0.rate, 1);\n+    assert_eq!(w0.parameters.len(), 0);\n+\n+    let w2 = &workload.workloads[2];\n+    // check the second workload\n+    assert_eq!(w2.method, \"calculate\");\n+    assert_eq!(w2.rate, 1);\n+    assert_eq!(w2.parameters.len(), 6);\n+\n+    // Check that the first parameter of the second workload was parsed correctly","path":"src/config.rs","position":319,"original_position":319,"commit_id":"29f11c03ec81213ed796dd51762d35bca38ee960","original_commit_id":"29f11c03ec81213ed796dd51762d35bca38ee960","user":{"login":"brayniac","id":10478487,"avatar_url":"https://avatars.githubusercontent.com/u/10478487?v=3","gravatar_id":"","url":"https://api.github.com/users/brayniac","html_url":"https://github.com/brayniac","followers_url":"https://api.github.com/users/brayniac/followers","following_url":"https://api.github.com/users/brayniac/following{/other_user}","gists_url":"https://api.github.com/users/brayniac/gists{/gist_id}","starred_url":"https://api.github.com/users/brayniac/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brayniac/subscriptions","organizations_url":"https://api.github.com/users/brayniac/orgs","repos_url":"https://api.github.com/users/brayniac/repos","events_url":"https://api.github.com/users/brayniac/events{/privacy}","received_events_url":"https://api.github.com/users/brayniac/received_events","type":"User","site_admin":false},"body":"same as line 261","created_at":"2016-04-04T15:35:51Z","updated_at":"2016-04-04T15:35:51Z","html_url":"https://github.com/twitter/rpc-perf/pull/69#discussion_r58396316","pull_request_url":"https://api.github.com/repos/twitter/rpc-perf/pulls/69","_links":{"self":{"href":"https://api.github.com/repos/twitter/rpc-perf/pulls/comments/58396316"},"html":{"href":"https://github.com/twitter/rpc-perf/pull/69#discussion_r58396316"},"pull_request":{"href":"https://api.github.com/repos/twitter/rpc-perf/pulls/69"}}},"pull_request":{"url":"https://api.github.com/repos/twitter/rpc-perf/pulls/69","id":65087873,"html_url":"https://github.com/twitter/rpc-perf/pull/69","diff_url":"https://github.com/twitter/rpc-perf/pull/69.diff","patch_url":"https://github.com/twitter/rpc-perf/pull/69.patch","issue_url":"https://api.github.com/repos/twitter/rpc-perf/issues/69","number":69,"state":"open","locked":false,"title":"WIP: Clean up some of the config logic","user":{"login":"bryce-anderson","id":2948506,"avatar_url":"https://avatars.githubusercontent.com/u/2948506?v=3","gravatar_id":"","url":"https://api.github.com/users/bryce-anderson","html_url":"https://github.com/bryce-anderson","followers_url":"https://api.github.com/users/bryce-anderson/followers","following_url":"https://api.github.com/users/bryce-anderson/following{/other_user}","gists_url":"https://api.github.com/users/bryce-anderson/gists{/gist_id}","starred_url":"https://api.github.com/users/bryce-anderson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bryce-anderson/subscriptions","organizations_url":"https://api.github.com/users/bryce-anderson/orgs","repos_url":"https://api.github.com/users/bryce-anderson/repos","events_url":"https://api.github.com/users/bryce-anderson/events{/privacy}","received_events_url":"https://api.github.com/users/bryce-anderson/received_events","type":"User","site_admin":false},"body":"Break down the extraction process.\r\nAssert structure better.\r\nAvoid panicing on bad config.","created_at":"2016-04-04T01:30:47Z","updated_at":"2016-04-04T15:35:51Z","closed_at":null,"merged_at":null,"merge_commit_sha":"69a4e84a80564ef8e5b86dc26ecafb0dcac9a90e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/twitter/rpc-perf/pulls/69/commits","review_comments_url":"https://api.github.com/repos/twitter/rpc-perf/pulls/69/comments","review_comment_url":"https://api.github.com/repos/twitter/rpc-perf/pulls/comments{/number}","comments_url":"https://api.github.com/repos/twitter/rpc-perf/issues/69/comments","statuses_url":"https://api.github.com/repos/twitter/rpc-perf/statuses/29f11c03ec81213ed796dd51762d35bca38ee960","head":{"label":"bryce-anderson:config","ref":"config","sha":"29f11c03ec81213ed796dd51762d35bca38ee960","user":{"login":"bryce-anderson","id":2948506,"avatar_url":"https://avatars.githubusercontent.com/u/2948506?v=3","gravatar_id":"","url":"https://api.github.com/users/bryce-anderson","html_url":"https://github.com/bryce-anderson","followers_url":"https://api.github.com/users/bryce-anderson/followers","following_url":"https://api.github.com/users/bryce-anderson/following{/other_user}","gists_url":"https://api.github.com/users/bryce-anderson/gists{/gist_id}","starred_url":"https://api.github.com/users/bryce-anderson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bryce-anderson/subscriptions","organizations_url":"https://api.github.com/users/bryce-anderson/orgs","repos_url":"https://api.github.com/users/bryce-anderson/repos","events_url":"https://api.github.com/users/bryce-anderson/events{/privacy}","received_events_url":"https://api.github.com/users/bryce-anderson/received_events","type":"User","site_admin":false},"repo":{"id":51156515,"name":"rpc-perf","full_name":"bryce-anderson/rpc-perf","owner":{"login":"bryce-anderson","id":2948506,"avatar_url":"https://avatars.githubusercontent.com/u/2948506?v=3","gravatar_id":"","url":"https://api.github.com/users/bryce-anderson","html_url":"https://github.com/bryce-anderson","followers_url":"https://api.github.com/users/bryce-anderson/followers","following_url":"https://api.github.com/users/bryce-anderson/following{/other_user}","gists_url":"https://api.github.com/users/bryce-anderson/gists{/gist_id}","starred_url":"https://api.github.com/users/bryce-anderson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bryce-anderson/subscriptions","organizations_url":"https://api.github.com/users/bryce-anderson/orgs","repos_url":"https://api.github.com/users/bryce-anderson/repos","events_url":"https://api.github.com/users/bryce-anderson/events{/privacy}","received_events_url":"https://api.github.com/users/bryce-anderson/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bryce-anderson/rpc-perf","description":"A tool for benchmarking RPC services","fork":true,"url":"https://api.github.com/repos/bryce-anderson/rpc-perf","forks_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/forks","keys_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/teams","hooks_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/hooks","issue_events_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/issues/events{/number}","events_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/events","assignees_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/assignees{/user}","branches_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/branches{/branch}","tags_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/tags","blobs_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/git/refs{/sha}","trees_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/statuses/{sha}","languages_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/languages","stargazers_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/stargazers","contributors_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/contributors","subscribers_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/subscribers","subscription_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/subscription","commits_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/commits{/sha}","git_commits_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/git/commits{/sha}","comments_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/comments{/number}","issue_comment_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/issues/comments{/number}","contents_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/contents/{+path}","compare_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/merges","archive_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/downloads","issues_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/issues{/number}","pulls_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/pulls{/number}","milestones_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/milestones{/number}","notifications_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/labels{/name}","releases_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/releases{/id}","deployments_url":"https://api.github.com/repos/bryce-anderson/rpc-perf/deployments","created_at":"2016-02-05T15:55:50Z","updated_at":"2016-02-05T15:55:51Z","pushed_at":"2016-04-04T13:23:17Z","git_url":"git://github.com/bryce-anderson/rpc-perf.git","ssh_url":"git@github.com:bryce-anderson/rpc-perf.git","clone_url":"https://github.com/bryce-anderson/rpc-perf.git","svn_url":"https://github.com/bryce-anderson/rpc-perf","homepage":null,"size":182,"stargazers_count":0,"watchers_count":0,"language":"Rust","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":"twitter:master","ref":"master","sha":"4de381e3d64fccb8db6d42ea97178291f44fa924","user":{"login":"twitter","id":50278,"avatar_url":"https://avatars.githubusercontent.com/u/50278?v=3","gravatar_id":"","url":"https://api.github.com/users/twitter","html_url":"https://github.com/twitter","followers_url":"https://api.github.com/users/twitter/followers","following_url":"https://api.github.com/users/twitter/following{/other_user}","gists_url":"https://api.github.com/users/twitter/gists{/gist_id}","starred_url":"https://api.github.com/users/twitter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitter/subscriptions","organizations_url":"https://api.github.com/users/twitter/orgs","repos_url":"https://api.github.com/users/twitter/repos","events_url":"https://api.github.com/users/twitter/events{/privacy}","received_events_url":"https://api.github.com/users/twitter/received_events","type":"Organization","site_admin":false},"repo":{"id":46767238,"name":"rpc-perf","full_name":"twitter/rpc-perf","owner":{"login":"twitter","id":50278,"avatar_url":"https://avatars.githubusercontent.com/u/50278?v=3","gravatar_id":"","url":"https://api.github.com/users/twitter","html_url":"https://github.com/twitter","followers_url":"https://api.github.com/users/twitter/followers","following_url":"https://api.github.com/users/twitter/following{/other_user}","gists_url":"https://api.github.com/users/twitter/gists{/gist_id}","starred_url":"https://api.github.com/users/twitter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitter/subscriptions","organizations_url":"https://api.github.com/users/twitter/orgs","repos_url":"https://api.github.com/users/twitter/repos","events_url":"https://api.github.com/users/twitter/events{/privacy}","received_events_url":"https://api.github.com/users/twitter/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/twitter/rpc-perf","description":"A tool for benchmarking RPC services","fork":false,"url":"https://api.github.com/repos/twitter/rpc-perf","forks_url":"https://api.github.com/repos/twitter/rpc-perf/forks","keys_url":"https://api.github.com/repos/twitter/rpc-perf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/twitter/rpc-perf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/twitter/rpc-perf/teams","hooks_url":"https://api.github.com/repos/twitter/rpc-perf/hooks","issue_events_url":"https://api.github.com/repos/twitter/rpc-perf/issues/events{/number}","events_url":"https://api.github.com/repos/twitter/rpc-perf/events","assignees_url":"https://api.github.com/repos/twitter/rpc-perf/assignees{/user}","branches_url":"https://api.github.com/repos/twitter/rpc-perf/branches{/branch}","tags_url":"https://api.github.com/repos/twitter/rpc-perf/tags","blobs_url":"https://api.github.com/repos/twitter/rpc-perf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/twitter/rpc-perf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/twitter/rpc-perf/git/refs{/sha}","trees_url":"https://api.github.com/repos/twitter/rpc-perf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/twitter/rpc-perf/statuses/{sha}","languages_url":"https://api.github.com/repos/twitter/rpc-perf/languages","stargazers_url":"https://api.github.com/repos/twitter/rpc-perf/stargazers","contributors_url":"https://api.github.com/repos/twitter/rpc-perf/contributors","subscribers_url":"https://api.github.com/repos/twitter/rpc-perf/subscribers","subscription_url":"https://api.github.com/repos/twitter/rpc-perf/subscription","commits_url":"https://api.github.com/repos/twitter/rpc-perf/commits{/sha}","git_commits_url":"https://api.github.com/repos/twitter/rpc-perf/git/commits{/sha}","comments_url":"https://api.github.com/repos/twitter/rpc-perf/comments{/number}","issue_comment_url":"https://api.github.com/repos/twitter/rpc-perf/issues/comments{/number}","contents_url":"https://api.github.com/repos/twitter/rpc-perf/contents/{+path}","compare_url":"https://api.github.com/repos/twitter/rpc-perf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/twitter/rpc-perf/merges","archive_url":"https://api.github.com/repos/twitter/rpc-perf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/twitter/rpc-perf/downloads","issues_url":"https://api.github.com/repos/twitter/rpc-perf/issues{/number}","pulls_url":"https://api.github.com/repos/twitter/rpc-perf/pulls{/number}","milestones_url":"https://api.github.com/repos/twitter/rpc-perf/milestones{/number}","notifications_url":"https://api.github.com/repos/twitter/rpc-perf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/twitter/rpc-perf/labels{/name}","releases_url":"https://api.github.com/repos/twitter/rpc-perf/releases{/id}","deployments_url":"https://api.github.com/repos/twitter/rpc-perf/deployments","created_at":"2015-11-24T04:39:03Z","updated_at":"2016-04-02T22:25:12Z","pushed_at":"2016-04-04T13:23:17Z","git_url":"git://github.com/twitter/rpc-perf.git","ssh_url":"git@github.com:twitter/rpc-perf.git","clone_url":"https://github.com/twitter/rpc-perf.git","svn_url":"https://github.com/twitter/rpc-perf","homepage":null,"size":168,"stargazers_count":132,"watchers_count":132,"language":"Rust","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7,"mirror_url":null,"open_issues_count":3,"forks":7,"open_issues":3,"watchers":132,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/twitter/rpc-perf/pulls/69"},"html":{"href":"https://github.com/twitter/rpc-perf/pull/69"},"issue":{"href":"https://api.github.com/repos/twitter/rpc-perf/issues/69"},"comments":{"href":"https://api.github.com/repos/twitter/rpc-perf/issues/69/comments"},"review_comments":{"href":"https://api.github.com/repos/twitter/rpc-perf/pulls/69/comments"},"review_comment":{"href":"https://api.github.com/repos/twitter/rpc-perf/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/twitter/rpc-perf/pulls/69/commits"},"statuses":{"href":"https://api.github.com/repos/twitter/rpc-perf/statuses/29f11c03ec81213ed796dd51762d35bca38ee960"}}}}