{"action":"created","comment":{"url":"https://api.github.com/repos/square/metrics/pulls/comments/31463800","id":31463800,"diff_hunk":"@@ -0,0 +1,210 @@\n+// Copyright 2015 Square Inc.\n+//\n+// Licensed under the Apache License, Version 2.0 (the \"License\");\n+// you may not use this file except in compliance with the License.\n+// You may obtain a copy of the License at\n+//\n+// http://www.apache.org/licenses/LICENSE-2.0\n+//\n+// Unless required by applicable law or agreed to in writing, software\n+// distributed under the License is distributed on an \"AS IS\" BASIS,\n+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+// See the License for the specific language governing permissions and\n+// limitations under the License.\n+\n+package query\n+\n+// This file culminates in the definition of `aggregateBy`, which takes a SeriesList and an Aggregator and a list of tags,\n+// and produces an aggregated SeriesList with one list per group, each group having been aggregated into it.\n+\n+import (\n+\t\"math\"\n+\n+\t\"github.com/square/metrics/api\"\n+)\n+\n+type group struct {\n+\tList   []api.Timeseries\n+\tTagSet api.TagSet\n+}\n+\n+type groupResult struct {\n+\tResults []group\n+}\n+\n+// If the given group will accept this given series (since it belongs to this group)\n+// then bucketValid will return true.\n+func bucketValid(left api.TagSet, right api.TagSet, tags []string) bool {\n+\tfor _, tag := range tags {\n+\t\tif left[tag] != right[tag] {\n+\t\t\treturn false\n+\t\t}\n+\t}\n+\treturn true\n+}\n+\n+// Adds the series to the corresponding bucket, possibly modifying the input `rows` and returning a new list.\n+func addToBucket(rows []group, series api.Timeseries, tags []string) []group {\n+\t// First we delete all tags with names other than those found in 'tags'\n+\tnewTags := api.NewTagSet()\n+\tfor _, tag := range tags {\n+\t\tnewTags[tag] = series.TagSet[tag]\n+\t}\n+\t// replace series' TagSet with newTags\n+\tseries.TagSet = newTags\n+\n+\t// Next, find the best bucket for this series:\n+\tfor i, row := range rows {\n+\t\tif bucketValid(row.TagSet, series.TagSet, tags) {\n+\t\t\trows[i].List = append(rows[i].List, series)\n+\t\t\treturn rows\n+\t\t}\n+\t}\n+\t// Otherwise, no bucket yet exists\n+\treturn append(rows, group{\n+\t\t[]api.Timeseries{series},\n+\t\tnewTags,\n+\t})\n+}\n+\n+// Groups the given SeriesList by tags, producing a list of lists (of type groupResult)\n+func groupBy(list api.SeriesList, tags []string) []group {\n+\tresult := []group{}\n+\tfor _, series := range list.Series {\n+\t\tresult = addToBucket(result, series, tags)\n+\t}\n+\treturn result\n+}\n+\n+// The aggregator interface is the public-facing way in which values are aggregated.\n+// Aggregator objects are required to perform aggregation (max, min, range, mean, sum, etc.)\n+// Their interface is the `aggregate` method, which takes an array of floats.\n+type aggregator interface {\n+\taggregate([]float64) float64\n+}\n+\n+type sumAggregator struct {\n+}\n+\n+var _ aggregator = sumAggregator{}\n+\n+// The `aggregate()` for sum finds the sum of the given array.\n+func (aggregator sumAggregator) aggregate(array []float64) float64 {\n+\tsum := 0.0\n+\tfor _, v := range array {\n+\t\tsum += v\n+\t}\n+\treturn sum\n+}\n+\n+// A mean aggregator is highly similar to a sum aggregator.\n+// It computes the aggregate mean for a seriesList\n+type meanAggregator struct {\n+}\n+\n+var _ aggregator = meanAggregator{}\n+\n+// The mean aggregator returns the mean of the given array\n+func (aggregator meanAggregator) aggregate(array []float64) float64 {\n+\tif len(array) == 0 {\n+\t\t// The mean of an empty list is not well-defined\n+\t\treturn math.NaN()\n+\t}\n+\tsum := 0.0\n+\tfor _, v := range array {\n+\t\tsum += v\n+\t}\n+\treturn sum / float64(len(array))\n+}\n+\n+// The min aggregator is an aggregator that computes the aggregate minimum for a seriesList\n+type minAggregator struct {\n+}\n+\n+var _ aggregator = minAggregator{}\n+\n+func (aggregator minAggregator) aggregate(array []float64) float64 {\n+\tif len(array) == 0 {\n+\t\t// The minimum of an empty list is not well-defined\n+\t\treturn math.NaN()\n+\t}\n+\tmin := array[0]\n+\tfor _, v := range array {\n+\t\tmin = math.Min(min, v)\n+\t}\n+\treturn min\n+}\n+\n+// The maxAggregator is an aggregator that computes the aggregate maximum for a seriesList\n+type maxAggregator struct {\n+}\n+\n+var _ aggregator = maxAggregator{}\n+\n+func (aggregator maxAggregator) aggregate(array []float64) float64 {\n+\tif len(array) == 0 {\n+\t\t// The maximum of an empty list is not well-defined\n+\t\treturn math.NaN()\n+\t}\n+\tmax := array[0]\n+\tfor _, v := range array {\n+\t\tmax = math.Max(max, v)\n+\t}\n+\treturn max\n+}\n+\n+// applyAggregation takes an aggregation function ( [float64] => float64 ) and applies it to a given list of Timeseries\n+// the list must be non-empty, or an error is returned\n+func applyAggregation(group group, aggregator aggregator) (api.Timeseries, error) {\n+\tlist := group.List\n+\ttagSet := group.TagSet\n+\n+\tif len(list) == 0 {","path":"query/aggregate.go","position":162,"original_position":162,"commit_id":"43b4ca2bbb4e09431e5c7e17c230b69e3247b557","original_commit_id":"43b4ca2bbb4e09431e5c7e17c230b69e3247b557","user":{"login":"achow","id":1609236,"avatar_url":"https://avatars.githubusercontent.com/u/1609236?v=3","gravatar_id":"","url":"https://api.github.com/users/achow","html_url":"https://github.com/achow","followers_url":"https://api.github.com/users/achow/followers","following_url":"https://api.github.com/users/achow/following{/other_user}","gists_url":"https://api.github.com/users/achow/gists{/gist_id}","starred_url":"https://api.github.com/users/achow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/achow/subscriptions","organizations_url":"https://api.github.com/users/achow/orgs","repos_url":"https://api.github.com/users/achow/repos","events_url":"https://api.github.com/users/achow/events{/privacy}","received_events_url":"https://api.github.com/users/achow/received_events","type":"User","site_admin":false},"body":"My feel is that aggregates on an empty list of Timeseries should just return an empty list. @jeeyoungk ?","created_at":"2015-06-01T20:00:37Z","updated_at":"2015-06-01T20:00:37Z","html_url":"https://github.com/square/metrics/pull/35#discussion_r31463800","pull_request_url":"https://api.github.com/repos/square/metrics/pulls/35","_links":{"self":{"href":"https://api.github.com/repos/square/metrics/pulls/comments/31463800"},"html":{"href":"https://github.com/square/metrics/pull/35#discussion_r31463800"},"pull_request":{"href":"https://api.github.com/repos/square/metrics/pulls/35"}}},"pull_request":{"url":"https://api.github.com/repos/square/metrics/pulls/35","id":36643942,"html_url":"https://github.com/square/metrics/pull/35","diff_url":"https://github.com/square/metrics/pull/35.diff","patch_url":"https://github.com/square/metrics/pull/35.patch","issue_url":"https://api.github.com/repos/square/metrics/issues/35","number":35,"state":"open","locked":false,"title":"Aggregation Function Implementation","user":{"login":"Nathan-Fenner","id":6179181,"avatar_url":"https://avatars.githubusercontent.com/u/6179181?v=3","gravatar_id":"","url":"https://api.github.com/users/Nathan-Fenner","html_url":"https://github.com/Nathan-Fenner","followers_url":"https://api.github.com/users/Nathan-Fenner/followers","following_url":"https://api.github.com/users/Nathan-Fenner/following{/other_user}","gists_url":"https://api.github.com/users/Nathan-Fenner/gists{/gist_id}","starred_url":"https://api.github.com/users/Nathan-Fenner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Nathan-Fenner/subscriptions","organizations_url":"https://api.github.com/users/Nathan-Fenner/orgs","repos_url":"https://api.github.com/users/Nathan-Fenner/repos","events_url":"https://api.github.com/users/Nathan-Fenner/events{/privacy}","received_events_url":"https://api.github.com/users/Nathan-Fenner/received_events","type":"User","site_admin":false},"body":"This pull request defines and implements aggregator functions which can be used to take a list of timeseries, group them by their tags, and then combine each group into a single timeseries using an aggregator like their sum, minimum, maximum, or mean.\r\n\r\n`func aggregateBy(list api.SeriesList, aggregator aggregator, tags []string) (api.SeriesList, error)` is the API for access by the rest of the query system, along with the aggregator struct types `sumAggregator`, `meanAggregator`, `maxAggregator`, and `minAggregator`.","created_at":"2015-06-01T16:07:16Z","updated_at":"2015-06-01T20:00:37Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7761bd8bb99a98b4bc703172051eb24271250837","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/square/metrics/pulls/35/commits","review_comments_url":"https://api.github.com/repos/square/metrics/pulls/35/comments","review_comment_url":"https://api.github.com/repos/square/metrics/pulls/comments{/number}","comments_url":"https://api.github.com/repos/square/metrics/issues/35/comments","statuses_url":"https://api.github.com/repos/square/metrics/statuses/43b4ca2bbb4e09431e5c7e17c230b69e3247b557","head":{"label":"square:nathanf-aggregation-implementation","ref":"nathanf-aggregation-implementation","sha":"43b4ca2bbb4e09431e5c7e17c230b69e3247b557","user":{"login":"square","id":82592,"avatar_url":"https://avatars.githubusercontent.com/u/82592?v=3","gravatar_id":"","url":"https://api.github.com/users/square","html_url":"https://github.com/square","followers_url":"https://api.github.com/users/square/followers","following_url":"https://api.github.com/users/square/following{/other_user}","gists_url":"https://api.github.com/users/square/gists{/gist_id}","starred_url":"https://api.github.com/users/square/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/square/subscriptions","organizations_url":"https://api.github.com/users/square/orgs","repos_url":"https://api.github.com/users/square/repos","events_url":"https://api.github.com/users/square/events{/privacy}","received_events_url":"https://api.github.com/users/square/received_events","type":"Organization","site_admin":false},"repo":{"id":34279409,"name":"metrics","full_name":"square/metrics","owner":{"login":"square","id":82592,"avatar_url":"https://avatars.githubusercontent.com/u/82592?v=3","gravatar_id":"","url":"https://api.github.com/users/square","html_url":"https://github.com/square","followers_url":"https://api.github.com/users/square/followers","following_url":"https://api.github.com/users/square/following{/other_user}","gists_url":"https://api.github.com/users/square/gists{/gist_id}","starred_url":"https://api.github.com/users/square/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/square/subscriptions","organizations_url":"https://api.github.com/users/square/orgs","repos_url":"https://api.github.com/users/square/repos","events_url":"https://api.github.com/users/square/events{/privacy}","received_events_url":"https://api.github.com/users/square/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/square/metrics","description":"Metrics Query Engine","fork":false,"url":"https://api.github.com/repos/square/metrics","forks_url":"https://api.github.com/repos/square/metrics/forks","keys_url":"https://api.github.com/repos/square/metrics/keys{/key_id}","collaborators_url":"https://api.github.com/repos/square/metrics/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/square/metrics/teams","hooks_url":"https://api.github.com/repos/square/metrics/hooks","issue_events_url":"https://api.github.com/repos/square/metrics/issues/events{/number}","events_url":"https://api.github.com/repos/square/metrics/events","assignees_url":"https://api.github.com/repos/square/metrics/assignees{/user}","branches_url":"https://api.github.com/repos/square/metrics/branches{/branch}","tags_url":"https://api.github.com/repos/square/metrics/tags","blobs_url":"https://api.github.com/repos/square/metrics/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/square/metrics/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/square/metrics/git/refs{/sha}","trees_url":"https://api.github.com/repos/square/metrics/git/trees{/sha}","statuses_url":"https://api.github.com/repos/square/metrics/statuses/{sha}","languages_url":"https://api.github.com/repos/square/metrics/languages","stargazers_url":"https://api.github.com/repos/square/metrics/stargazers","contributors_url":"https://api.github.com/repos/square/metrics/contributors","subscribers_url":"https://api.github.com/repos/square/metrics/subscribers","subscription_url":"https://api.github.com/repos/square/metrics/subscription","commits_url":"https://api.github.com/repos/square/metrics/commits{/sha}","git_commits_url":"https://api.github.com/repos/square/metrics/git/commits{/sha}","comments_url":"https://api.github.com/repos/square/metrics/comments{/number}","issue_comment_url":"https://api.github.com/repos/square/metrics/issues/comments{/number}","contents_url":"https://api.github.com/repos/square/metrics/contents/{+path}","compare_url":"https://api.github.com/repos/square/metrics/compare/{base}...{head}","merges_url":"https://api.github.com/repos/square/metrics/merges","archive_url":"https://api.github.com/repos/square/metrics/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/square/metrics/downloads","issues_url":"https://api.github.com/repos/square/metrics/issues{/number}","pulls_url":"https://api.github.com/repos/square/metrics/pulls{/number}","milestones_url":"https://api.github.com/repos/square/metrics/milestones{/number}","notifications_url":"https://api.github.com/repos/square/metrics/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/square/metrics/labels{/name}","releases_url":"https://api.github.com/repos/square/metrics/releases{/id}","created_at":"2015-04-20T18:43:35Z","updated_at":"2015-05-27T19:23:09Z","pushed_at":"2015-06-01T18:49:20Z","git_url":"git://github.com/square/metrics.git","ssh_url":"git@github.com:square/metrics.git","clone_url":"https://github.com/square/metrics.git","svn_url":"https://github.com/square/metrics","homepage":"","size":1702,"stargazers_count":2,"watchers_count":2,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":4,"forks":0,"open_issues":4,"watchers":2,"default_branch":"master"}},"base":{"label":"square:master","ref":"master","sha":"0bf30b510063f13edd0d674616df79eaa905e015","user":{"login":"square","id":82592,"avatar_url":"https://avatars.githubusercontent.com/u/82592?v=3","gravatar_id":"","url":"https://api.github.com/users/square","html_url":"https://github.com/square","followers_url":"https://api.github.com/users/square/followers","following_url":"https://api.github.com/users/square/following{/other_user}","gists_url":"https://api.github.com/users/square/gists{/gist_id}","starred_url":"https://api.github.com/users/square/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/square/subscriptions","organizations_url":"https://api.github.com/users/square/orgs","repos_url":"https://api.github.com/users/square/repos","events_url":"https://api.github.com/users/square/events{/privacy}","received_events_url":"https://api.github.com/users/square/received_events","type":"Organization","site_admin":false},"repo":{"id":34279409,"name":"metrics","full_name":"square/metrics","owner":{"login":"square","id":82592,"avatar_url":"https://avatars.githubusercontent.com/u/82592?v=3","gravatar_id":"","url":"https://api.github.com/users/square","html_url":"https://github.com/square","followers_url":"https://api.github.com/users/square/followers","following_url":"https://api.github.com/users/square/following{/other_user}","gists_url":"https://api.github.com/users/square/gists{/gist_id}","starred_url":"https://api.github.com/users/square/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/square/subscriptions","organizations_url":"https://api.github.com/users/square/orgs","repos_url":"https://api.github.com/users/square/repos","events_url":"https://api.github.com/users/square/events{/privacy}","received_events_url":"https://api.github.com/users/square/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/square/metrics","description":"Metrics Query Engine","fork":false,"url":"https://api.github.com/repos/square/metrics","forks_url":"https://api.github.com/repos/square/metrics/forks","keys_url":"https://api.github.com/repos/square/metrics/keys{/key_id}","collaborators_url":"https://api.github.com/repos/square/metrics/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/square/metrics/teams","hooks_url":"https://api.github.com/repos/square/metrics/hooks","issue_events_url":"https://api.github.com/repos/square/metrics/issues/events{/number}","events_url":"https://api.github.com/repos/square/metrics/events","assignees_url":"https://api.github.com/repos/square/metrics/assignees{/user}","branches_url":"https://api.github.com/repos/square/metrics/branches{/branch}","tags_url":"https://api.github.com/repos/square/metrics/tags","blobs_url":"https://api.github.com/repos/square/metrics/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/square/metrics/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/square/metrics/git/refs{/sha}","trees_url":"https://api.github.com/repos/square/metrics/git/trees{/sha}","statuses_url":"https://api.github.com/repos/square/metrics/statuses/{sha}","languages_url":"https://api.github.com/repos/square/metrics/languages","stargazers_url":"https://api.github.com/repos/square/metrics/stargazers","contributors_url":"https://api.github.com/repos/square/metrics/contributors","subscribers_url":"https://api.github.com/repos/square/metrics/subscribers","subscription_url":"https://api.github.com/repos/square/metrics/subscription","commits_url":"https://api.github.com/repos/square/metrics/commits{/sha}","git_commits_url":"https://api.github.com/repos/square/metrics/git/commits{/sha}","comments_url":"https://api.github.com/repos/square/metrics/comments{/number}","issue_comment_url":"https://api.github.com/repos/square/metrics/issues/comments{/number}","contents_url":"https://api.github.com/repos/square/metrics/contents/{+path}","compare_url":"https://api.github.com/repos/square/metrics/compare/{base}...{head}","merges_url":"https://api.github.com/repos/square/metrics/merges","archive_url":"https://api.github.com/repos/square/metrics/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/square/metrics/downloads","issues_url":"https://api.github.com/repos/square/metrics/issues{/number}","pulls_url":"https://api.github.com/repos/square/metrics/pulls{/number}","milestones_url":"https://api.github.com/repos/square/metrics/milestones{/number}","notifications_url":"https://api.github.com/repos/square/metrics/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/square/metrics/labels{/name}","releases_url":"https://api.github.com/repos/square/metrics/releases{/id}","created_at":"2015-04-20T18:43:35Z","updated_at":"2015-05-27T19:23:09Z","pushed_at":"2015-06-01T18:49:20Z","git_url":"git://github.com/square/metrics.git","ssh_url":"git@github.com:square/metrics.git","clone_url":"https://github.com/square/metrics.git","svn_url":"https://github.com/square/metrics","homepage":"","size":1702,"stargazers_count":2,"watchers_count":2,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":4,"forks":0,"open_issues":4,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/square/metrics/pulls/35"},"html":{"href":"https://github.com/square/metrics/pull/35"},"issue":{"href":"https://api.github.com/repos/square/metrics/issues/35"},"comments":{"href":"https://api.github.com/repos/square/metrics/issues/35/comments"},"review_comments":{"href":"https://api.github.com/repos/square/metrics/pulls/35/comments"},"review_comment":{"href":"https://api.github.com/repos/square/metrics/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/square/metrics/pulls/35/commits"},"statuses":{"href":"https://api.github.com/repos/square/metrics/statuses/43b4ca2bbb4e09431e5c7e17c230b69e3247b557"}}}}