{"action":"created","comment":{"url":"https://api.github.com/repos/influxdata/kapacitor/pulls/comments/61435367","id":61435367,"diff_hunk":"@@ -0,0 +1,547 @@\n+package eval\n+\n+import (\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"regexp\"\n+\n+\t\"github.com/influxdata/kapacitor/tick\"\n+)\n+\n+// ErrTypeGuardFailed is returned when a speicifc value type is requeste thorugh NodeValueAcessor (for example: \"Float64Value\")\n+// when the node doesn't support the given type, for example \"Float64Value\" is called on BoolNode\n+var ErrTypeGuardFailed = errors.New(\"Type guard failed: Requested type is not supported on the given NodeValueAccessor\")\n+\n+// NodeValueAccessor provides a generic way for trying to fetch\n+// node value, if a speicifc type is requested (so Value isn't called, the *Value is called) ErrTypeGuardFailed must be returned\n+type NodeValueAccessor interface {\n+\tValue(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error)\n+\n+\tFloat64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error)\n+\tInt64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error)\n+\tStringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error)\n+\tBoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error)\n+\tRegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error)\n+}\n+\n+//\n+// ValueContainer\n+\n+type ValueContainer struct {\n+\tRealValue interface{}\n+}\n+\n+func (n ValueContainer) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\treturn n.RealValue, nil\n+}\n+\n+func (n ValueContainer) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif regexValue, isRegex := refValue.(*regexp.Regexp); isRegex {\n+\t\treturn regexValue, nil\n+\t}\n+\n+\treturn nil, ErrTypeGuardFailed\n+}\n+\n+func (n ValueContainer) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn \"\", err\n+\t}\n+\n+\tif stringValue, isString := refValue.(string); isString {\n+\t\treturn stringValue, nil\n+\t}\n+\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n ValueContainer) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn float64(0), err\n+\t}\n+\n+\tif float64Value, isFloat64 := refValue.(float64); isFloat64 {\n+\t\treturn float64Value, nil\n+\t}\n+\n+\treturn float64(0), ErrTypeGuardFailed\n+}\n+\n+func (n ValueContainer) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn int64(0), err\n+\t}\n+\n+\tif int64Value, isInt64 := refValue.(int64); isInt64 {\n+\t\treturn int64Value, nil\n+\t}\n+\n+\treturn int64(0), ErrTypeGuardFailed\n+}\n+\n+func (n ValueContainer) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn false, err\n+\t}\n+\n+\tif boolValue, isBool := refValue.(bool); isBool {\n+\t\treturn boolValue, nil\n+\t}\n+\n+\treturn false, ErrTypeGuardFailed\n+}\n+\n+//-----------------------------------------\n+// BoolNode NodeValueAcessor Implementation\n+\n+func (n *EvalBoolNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\treturn n.Node.Bool, nil\n+}\n+\n+func (n *EvalBoolNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\treturn n.Node.Bool, nil\n+}\n+\n+func (n *EvalBoolNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\treturn float64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalBoolNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\treturn int64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalBoolNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n *EvalBoolNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\treturn nil, ErrTypeGuardFailed\n+}\n+\n+//-----------------------------------------\n+// NumberNode NodeValueAcessor Implementation\n+\n+func (n *EvalNumberNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\tif n.Node.IsFloat {\n+\t\treturn n.Node.Float64, nil\n+\t}\n+\n+\tif n.Node.IsInt {\n+\t\treturn n.Node.Int64, nil\n+\t}\n+\n+\treturn nil, ErrTypeGuardFailed\n+}\n+\n+func (n *EvalNumberNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\tif n.Node.IsFloat {\n+\t\treturn n.Node.Float64, nil\n+\t}\n+\n+\treturn float64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalNumberNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\tif n.Node.IsInt {\n+\t\treturn n.Node.Int64, nil\n+\t}\n+\n+\treturn int64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalNumberNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n *EvalNumberNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\treturn false, ErrTypeGuardFailed\n+}\n+func (n *EvalNumberNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\treturn nil, ErrTypeGuardFailed\n+}\n+\n+//-----------------------------------------\n+// RegexNode NodeValueAcessor Implementation\n+\n+func (n *EvalRegexNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\treturn n.Node.Regex, nil\n+}\n+\n+func (n *EvalRegexNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\treturn n.Node.Regex, nil\n+}\n+\n+func (n *EvalRegexNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n *EvalRegexNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\treturn float64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalRegexNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\treturn int64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalRegexNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\treturn false, ErrTypeGuardFailed\n+}\n+\n+//-----------------------------------------\n+// StringNode NodeValueAcessor Implementation\n+\n+func (n *EvalStringNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\treturn n.Node.Literal, nil\n+}\n+\n+func (n *EvalStringNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\treturn n.Node.Literal, nil\n+}\n+\n+func (n *EvalStringNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\treturn float64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalStringNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\treturn int64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalStringNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\treturn false, ErrTypeGuardFailed\n+}\n+\n+func (n *EvalStringNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\treturn nil, ErrTypeGuardFailed\n+}\n+\n+//-----------------------------------------\n+// BinaryNode NodeValueAcessor Implementation\n+//\n+// it is safe to assume in BinaryNode speicific value functions to use EvalNum/EvalBool\n+// because we are using getConstantNodeType to do type inference, and it's really accurate\n+\n+func (n *EvalBinaryNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\tswitch getConstantNodeType(n.Node) {\n+\tcase TInt64:\n+\t\treturn n.Int64Value(scope, executionAux)\n+\tcase TFloat64:\n+\t\treturn n.Float64Value(scope, executionAux)\n+\tcase TBool:\n+\t\treturn n.BoolValue(scope, executionAux)\n+\tdefault:\n+\t\treturn nil, nil\n+\t}\n+}\n+\n+func (n *EvalBinaryNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\treturn nil, nil\n+}\n+\n+func (n *EvalBinaryNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n *EvalBinaryNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\tse, err := executionAux.ExpressionCache.GetOrCompile(n.Node)\n+\tif err != nil {\n+\t\treturn float64(0), err\n+\t}\n+\n+\tresult, err := se.EvalNum(scope)\n+\tif err != nil {\n+\t\treturn float64(0), err\n+\t}\n+\n+\treturn result.(float64), nil\n+}\n+\n+func (n *EvalBinaryNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\tse, err := executionAux.ExpressionCache.GetOrCompile(n.Node)\n+\tif err != nil {\n+\t\treturn int64(0), err\n+\t}\n+\n+\tresult, err := se.EvalNum(scope)\n+\tif err != nil {\n+\t\treturn int64(0), err\n+\t}\n+\n+\treturn result.(int64), nil\n+}\n+\n+func (n *EvalBinaryNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\tse, err := executionAux.ExpressionCache.GetOrCompile(n.Node)\n+\tif err != nil {\n+\t\treturn false, err\n+\t}\n+\n+\treturn se.EvalBool(scope)\n+}\n+\n+//-----------------------------------------\n+// BinaryNode NodeValueAcessor Implementation\n+//\n+// it is safe to assume in BinaryNode speicific value functions to use EvalNum/EvalBool\n+// because we are using getConstantNodeType to do type inference, and it's really accurate\n+\n+func (n *EvalUnaryNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\tswitch getConstantNodeType(n.Node) {\n+\tcase TInt64:\n+\t\treturn n.Int64Value(scope, executionAux)\n+\tcase TFloat64:\n+\t\treturn n.Float64Value(scope, executionAux)\n+\tcase TBool:\n+\t\treturn n.BoolValue(scope, executionAux)\n+\tdefault:\n+\t\treturn nil, nil\n+\t}\n+}\n+\n+func (n *EvalUnaryNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\treturn nil, nil\n+}\n+\n+func (n *EvalUnaryNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n *EvalUnaryNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\tif n.Node.Operator == tick.TokenMinus {\n+\t\t// PERF: we can use expression cache here, but this is too much..\n+\t\tnodeValueAccessor := createNodeValueAccessor(n.Node.Node)\n+\t\tif nodeValueAccessor == nil {\n+\t\t\treturn float64(0), fmt.Errorf(\"Got nil node value accessor for %v, this is invalid behavior - we expected the node to be NumberNode\", n.Node.Node)\n+\t\t}\n+\n+\t\tresult, err := nodeValueAccessor.Float64Value(scope, executionAux)\n+\t\tif err != nil {\n+\t\t\treturn float64(0), err\n+\t\t}\n+\n+\t\treturn -1 * result, nil\n+\t}\n+\n+\treturn float64(0), fmt.Errorf(\"unary node: invalid operator %v\", n.Node.Operator)\n+}\n+\n+func (n *EvalUnaryNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\tif n.Node.Operator == tick.TokenMinus {\n+\t\t// PERF: we can use expression cache here, but this is too much..\n+\t\tnodeValueAccessor := createNodeValueAccessor(n.Node.Node)\n+\t\tif nodeValueAccessor == nil {\n+\t\t\treturn int64(0), fmt.Errorf(\"Got nil node value accessor for %v, this is invalid behavior - we expected the node to be NumberNode\", n.Node.Node)\n+\t\t}\n+\n+\t\tresult, err := nodeValueAccessor.Int64Value(scope, executionAux)\n+\t\tif err != nil {\n+\t\t\treturn int64(0), err\n+\t\t}\n+\n+\t\treturn -1 * result, nil\n+\t}\n+\n+\treturn int64(0), fmt.Errorf(\"unary node: invalid operator %v\", n.Node.Operator)\n+}\n+\n+func (n *EvalUnaryNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\tif n.Node.Operator == tick.TokenNot {\n+\t\t// PERF: You might think to add some cache here from node to it's NodeValueAccessor or use the expression cache\n+\t\t// but the performance is neglibile - 8ns/op (this after trying to add NodeValueAccessor cache) for Benchmark_EvalBool_OneOperator_UnaryNode\n+\t\tnodeValueAccessor := createNodeValueAccessor(n.Node.Node)\n+\t\tif nodeValueAccessor == nil {\n+\t\t\treturn false, fmt.Errorf(\"Got nil node value accessor for %v, this is invalid behavior - we expected the node to be BoolNode\", n.Node.Node)\n+\t\t}\n+\n+\t\tresult, err := nodeValueAccessor.BoolValue(scope, executionAux)\n+\t\tif err != nil {\n+\t\t\treturn false, err\n+\t\t}\n+\n+\t\treturn !result, nil\n+\t}\n+\n+\treturn false, fmt.Errorf(\"unary node: invalid operator %v\", n.Node.Operator)\n+}\n+\n+//-----------------------------------------\n+// ReferenceNode NodeValueAcessor Implementation\n+\n+func (n *EvalReferenceNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\tvalue, err := scope.Get(n.Node.Reference)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\treturn value, nil\n+}\n+\n+func (n *EvalReferenceNode) RegexValue(scope *tick.Scope, executionAux ExecutionAux) (*regexp.Regexp, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif regexValue, isRegex := refValue.(*regexp.Regexp); isRegex {\n+\t\treturn regexValue, nil\n+\t}\n+\n+\treturn nil, ErrTypeGuardFailed\n+}\n+\n+func (n *EvalReferenceNode) StringValue(scope *tick.Scope, executionAux ExecutionAux) (string, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn \"\", err\n+\t}\n+\n+\tif stringValue, isString := refValue.(string); isString {\n+\t\treturn stringValue, nil\n+\t}\n+\n+\treturn \"\", ErrTypeGuardFailed\n+}\n+\n+func (n *EvalReferenceNode) Float64Value(scope *tick.Scope, executionAux ExecutionAux) (float64, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn float64(0), err\n+\t}\n+\n+\tif float64Value, isFloat64 := refValue.(float64); isFloat64 {\n+\t\treturn float64Value, nil\n+\t}\n+\n+\treturn float64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalReferenceNode) Int64Value(scope *tick.Scope, executionAux ExecutionAux) (int64, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn int64(0), err\n+\t}\n+\n+\tif int64Value, isInt64 := refValue.(int64); isInt64 {\n+\t\treturn int64Value, nil\n+\t}\n+\n+\treturn int64(0), ErrTypeGuardFailed\n+}\n+\n+func (n *EvalReferenceNode) BoolValue(scope *tick.Scope, executionAux ExecutionAux) (bool, error) {\n+\trefValue, err := n.Value(scope, executionAux)\n+\tif err != nil {\n+\t\treturn false, err\n+\t}\n+\n+\tif boolValue, isBool := refValue.(bool); isBool {\n+\t\treturn boolValue, nil\n+\t}\n+\n+\treturn false, ErrTypeGuardFailed\n+}\n+\n+//-----------------------------------------\n+// FunctionNode NodeValueAcessor Implementation\n+\n+func (n *EvalFunctionNode) Value(scope *tick.Scope, executionAux ExecutionAux) (interface{}, error) {\n+\targs := make([]interface{}, len(n.Node.Args))\n+\tfor i, argNode := range n.Node.Args {\n+\t\targNva := createNodeValueAccessor(argNode)","path":"tick/eval/node_value_acessors.go","position":null,"original_position":458,"commit_id":"0155ec0dcf67597bc7ebb6680227f1291a3f50ef","original_commit_id":"8f219dc7fc9cd1e99eca7e5793844d787ec67bc5","user":{"login":"yosiat","id":306253,"avatar_url":"https://avatars.githubusercontent.com/u/306253?v=3","gravatar_id":"","url":"https://api.github.com/users/yosiat","html_url":"https://github.com/yosiat","followers_url":"https://api.github.com/users/yosiat/followers","following_url":"https://api.github.com/users/yosiat/following{/other_user}","gists_url":"https://api.github.com/users/yosiat/gists{/gist_id}","starred_url":"https://api.github.com/users/yosiat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yosiat/subscriptions","organizations_url":"https://api.github.com/users/yosiat/orgs","repos_url":"https://api.github.com/users/yosiat/repos","events_url":"https://api.github.com/users/yosiat/events{/privacy}","received_events_url":"https://api.github.com/users/yosiat/received_events","type":"User","site_admin":false},"body":"@nathanielc It's covered now, with a dedicate test - https://github.com/yosiat/kapacitor/blob/compiled-stateful-expression/tick/eval/eval_function_node_test.go#L244,L281\r\n\r\n;) ","created_at":"2016-04-28T14:23:17Z","updated_at":"2016-04-28T14:23:17Z","html_url":"https://github.com/influxdata/kapacitor/pull/491#discussion_r61435367","pull_request_url":"https://api.github.com/repos/influxdata/kapacitor/pulls/491","_links":{"self":{"href":"https://api.github.com/repos/influxdata/kapacitor/pulls/comments/61435367"},"html":{"href":"https://github.com/influxdata/kapacitor/pull/491#discussion_r61435367"},"pull_request":{"href":"https://api.github.com/repos/influxdata/kapacitor/pulls/491"}}},"pull_request":{"url":"https://api.github.com/repos/influxdata/kapacitor/pulls/491","id":67648361,"html_url":"https://github.com/influxdata/kapacitor/pull/491","diff_url":"https://github.com/influxdata/kapacitor/pull/491.diff","patch_url":"https://github.com/influxdata/kapacitor/pull/491.patch","issue_url":"https://api.github.com/repos/influxdata/kapacitor/issues/491","number":491,"state":"open","locked":false,"title":"Compiled stateful expression","user":{"login":"yosiat","id":306253,"avatar_url":"https://avatars.githubusercontent.com/u/306253?v=3","gravatar_id":"","url":"https://api.github.com/users/yosiat","html_url":"https://github.com/yosiat","followers_url":"https://api.github.com/users/yosiat/followers","following_url":"https://api.github.com/users/yosiat/following{/other_user}","gists_url":"https://api.github.com/users/yosiat/gists{/gist_id}","starred_url":"https://api.github.com/users/yosiat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yosiat/subscriptions","organizations_url":"https://api.github.com/users/yosiat/orgs","repos_url":"https://api.github.com/users/yosiat/repos","events_url":"https://api.github.com/users/yosiat/events{/privacy}","received_events_url":"https://api.github.com/users/yosiat/received_events","type":"User","site_admin":false},"body":"Hi,\r\n\r\nThis is a one big pull request with the next bottom line changes:\r\n\r\n- Performance of evaluating stateful expression signifactly improved\r\n- Added 11 unit tests for stateful expression and coverage got up from 16.2% to 18.2%\r\n- **All tests are passing** - I changed all the usages of tick.NewStatefulExper to use the new one - and all integrations tests passed.\r\n- There a behaviour changes - priority to errors have changes, etc - but in my opinion they are not big\r\n- DurationNode is not supported\r\n- Currently, I didn't replaced the stateful expression with the new one.\r\n\r\n# Implementation\r\n_Those are explanations of the core algorithm, if there are more questions/clarifications requested, I will update this._\r\n\r\n## Basic explanation\r\nThe overall idea: Instead of using stack-based AST interpreter compiled the expressions to specialized functions.\r\nFor example: given this expression ``\"value\" > 8.0``, let's assume two assumptions:\r\n- \"value\" is float64\r\n- 8.0 is float64\r\n\r\n\r\nThe specializer will take this expression and will evantually run float64 > float64 all the time, instead of doing for every evaluation:\r\n- Type checking and guessing: checking the type of ref node and the right node type\r\n- Run through the whole AST tree\r\n\r\n## Deeper explanation\r\nFirst, let's set up simple terminology:\r\n* Dynamic Node - node that is value changes on runtime like FunctionNode and ReferenceNode\r\n* Constant Node - node that is value is constant for the whole lifetime of the tick script\r\n* Evaluation Function - evaluation function is the function that accepts three arguments: scope, left and right node (this is a simplified version)\r\n\r\nWhen we get a BinaryNode we determine if it's dynamic or constant - let's examine the dynamic case.\r\n\r\nIf this is dynamic node in the constructor (NewStatefulExpr) we set the evaluation function to be \"dynamic evaluation function\" otherwise\r\nwe fetch the matching evaluation function based on the nodes types and their operator.\r\n\r\nThe dynamic evaluation function is doing the next instructions (_this were the \"specialization\" happens_):\r\n* Read the values of the left and right node (for example: for a reference node we will access the scope and read the value)\r\n* Find a matching evaluation function based on the types we got and save it (in field in the StatefulExpression struct)\r\n* call EvalBool\r\n\r\n**The real meat is in EvalBool/EvalNum:**\r\n1. If the evaluation function is null it means that we have some error:\r\n 1. Type mismatch: int > string\r\n 2. Not a comparison/math operator: int - int\r\n 3. Invalid operator for type: bool > bool\r\n2. We have evaluation function and evaluate her - the evaluation function returns bool and error\r\n3. We examine the error if it's our special error (ErrTypeGuardFailed) that indicates we ran the wrong comparison function - this can happen on type changes - for example: \"value\" started as int64 and eventually changed to float6\r\n 1. If we have an error - go to dynamic evaluation - to specialise the evaluation function \r\n4. Return the results - bool and error\r\n\r\nIt's important to say: that we handle single nodes as well for example: EvalBool(BoolNode), etc.\r\n\r\n# Performance\r\nI ran the tests on MacBook Pro (13-inch, Late 2011) - i5 2.4ghz, 8GB RAM and 128GB SSD.\r\nThe tests ran with the flag of \"--count=5\" and compared using benchstat.\r\n\r\n## EvalBool Benchmarks\r\n```\r\nname                                                                       old time/op    new time/op    delta\r\n_EvalBool_OneOperator_UnaryNode_BoolNode-4                                    252ns ± 2%      68ns ± 1%   -73.02%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberFloat64_NumberFloat64-4                           540ns ± 2%      41ns ± 2%   -92.33%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberFloat64_NumberInt64-4                             550ns ± 3%      43ns ± 3%   -92.23%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberInt64_NumberInt64-4                               539ns ± 2%      40ns ± 3%   -92.56%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64-4                    524ns ± 3%      76ns ± 3%   -85.57%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_NumberInt64-4                      526ns ± 1%      78ns ± 6%   -85.21%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_ReferenceNodeFloat64-4             495ns ± 3%     121ns ± 2%   -75.46%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeFloat64_NumberFloat64-4     534ns ± 3%      94ns ± 3%   -82.37%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat64-4       2.98µs ± 1%    1.25µs ± 3%   -58.21%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeInt64_ReferenceNodeInt64-4                 503ns ± 3%     118ns ± 4%   -76.49%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt64-4         533ns ± 1%      89ns ± 4%   -83.23%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64-4           3.08µs ± 4%    1.25µs ± 3%   -59.33%  (p=0.008 n=5+5)\r\n\r\nname                                                                       old alloc/op   new alloc/op   delta\r\n_EvalBool_OneOperator_UnaryNode_BoolNode-4                                    18.0B ± 0%      8.0B ± 0%   -55.56%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberFloat64_NumberFloat64-4                           72.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberFloat64_NumberInt64-4                             72.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberInt64_NumberInt64-4                               72.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64-4                    64.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_NumberInt64-4                      64.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_ReferenceNodeFloat64-4             49.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeFloat64_NumberFloat64-4     64.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat64-4        64.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeInt64_ReferenceNodeInt64-4                 49.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt64-4         64.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64-4            64.0B ± 0%     0.0B ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n\r\nname                                                                       old allocs/op  new allocs/op  delta\r\n_EvalBool_OneOperator_UnaryNode_BoolNode-4                                     3.00 ± 0%      1.00 ± 0%   -66.67%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberFloat64_NumberFloat64-4                            5.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberFloat64_NumberInt64-4                              5.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_NumberInt64_NumberInt64-4                                5.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64-4                     4.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_NumberInt64-4                       4.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeFloat64_ReferenceNodeFloat64-4              3.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeFloat64_NumberFloat64-4      4.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat64-4         4.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperator_ReferenceNodeInt64_ReferenceNodeInt64-4                  3.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt64-4          4.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64-4             4.00 ± 0%     0.00 ±NaN%  -100.00%  (p=0.008 n=5+5)\r\n```\r\n\r\n## AlertTask benchmarks\r\n```\r\nname                     old time/op    new time/op    delta\r\n_T10_P500_AlertTask-4       138ms ± 5%     133ms ± 6%     ~     (p=0.421 n=5+5)\r\n_T10_P50000_AlertTask-4     13.7s ± 6%     13.1s ± 5%     ~     (p=0.421 n=5+5)\r\n_T1000_P500_AlertTask-4     13.7s ± 2%     13.0s ± 3%   -4.91%  (p=0.008 n=5+5)\r\n\r\nname                     old alloc/op   new alloc/op   delta\r\n_T10_P500_AlertTask-4      33.0MB ± 0%    32.0MB ± 0%   -2.85%  (p=0.008 n=5+5)\r\n_T10_P50000_AlertTask-4    3.36GB ± 0%    3.26GB ± 0%   -2.86%  (p=0.008 n=5+5)\r\n_T1000_P500_AlertTask-4    3.29GB ± 0%    3.19GB ± 0%   -2.90%  (p=0.008 n=5+5)\r\n\r\nname                     old allocs/op  new allocs/op  delta\r\n_T10_P500_AlertTask-4        466k ± 0%      408k ± 0%  -12.58%  (p=0.008 n=5+5)\r\n_T10_P50000_AlertTask-4     47.5M ± 0%     41.5M ± 0%  -12.62%  (p=0.008 n=5+5)\r\n_T1000_P500_AlertTask-4     46.1M ± 0%     40.2M ± 0%  -12.73%  (p=0.008 n=5+5)\r\n```\r\n\r\n# Questions / Notes\r\n## Tests\r\nI added more tests to stateful expression, to make sure we cover more and more cases.\r\nThe coverage for eval package is now 73.5%.\r\nI added those tests:\r\n* TestStatefulExpression_EvalBool_BinaryNodeWithDurationNode\r\n* TestStatefulExpression_EvalNum_FunctionWithTimeValue\r\n* TestStatefulExpression_Eval_NotSupportedNode\r\n* TestStatefulExpression_Eval_NodeAndEvalTypeNotMatching\r\n* TestStatefulExpression_EvalBool_BinaryNodeWithBoolUnaryNode\r\n* TestStatefulExpression_EvalBool_BinaryNodeWithNumericUnaryNode\r\n* TestStatefulExpression_EvalBool_TwoLevelsDeepBinaryWithEvalNum_Int64\r\n* TestStatefulExpression_EvalBool_TwoLevelsDeepBinaryWithEvalNum_Float64\r\n* TestStatefulExpression_EvalBool_SanityCallingFunction\r\n* TestStatefulExpression_EvalNum_SanityCallingFunctionWithArgs\r\n* TestStatefulExpression_EvalBool_SanityCallingFunctionWithArgs\r\n\r\n## Important\r\n**@nathanielc / pull request reviewer, please read those very carefully and answer them please!**\r\nThe notes/questions are ordered by importance:\r\n1. **Didn't tested** function return type changes - there is need to? If so, do we have function to do so? or should I need to create new one and stub it in?\r\n2. **Not supported** DurationNode - I saw the stateful expression did handle DurationNode, but I can't figure out where it's used - not in BinaryNode and not as single node (ex: EvalNum(DurationNode))\r\n3. **In StatefulExpression we are calling \"node.eval\"** - why so? in the new one we don't call this methods are all tests are passing, are we missing tests?.\r\n4. **Creating expression return error** - this is new \"behaviour\", compiling an expression can return an error, there is test for it: TestStatefulExpression_Eval_NotSupportedNode, examples: \r\n   1. passing invalid node to compile, example: commentnode\r\n   2. passing invalid node in binarynode\r\n\r\n5.@nathanielc - you requested to separate to packages as ast and etc, I didn't do this in this pull request, because it's too much big PR\r\n6. I can fix #490 pretty easily, do you want to?\r\n\r\n\r\n## Nice-To-Haves\r\nThose are nice to haves, maybe in this pull request and maybe another:\r\n\r\n- Debug logs for optimising: add debug log for when guard files and etc, can be useful in performance investigations\r\n- Performance optimisation (not related to this pr): In mergeFieldsAndTags we put all tags and fields in the scope, I think we can traverse the node AST and get a list of needed scope variables and then fetch them, in my opinion it can yield great performance improvement - _I will research this after this PR will get merged_\r\n\r\n\r\nFee, I finished 👍 \r\nThat was a really fun and educating experience, thanks @nathanielc for being open to changes :)\r\n- Yosi","created_at":"2016-04-24T20:08:53Z","updated_at":"2016-04-28T14:23:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a14e7197cc32f6cff11818ad808f2189ea8b0592","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/influxdata/kapacitor/pulls/491/commits","review_comments_url":"https://api.github.com/repos/influxdata/kapacitor/pulls/491/comments","review_comment_url":"https://api.github.com/repos/influxdata/kapacitor/pulls/comments{/number}","comments_url":"https://api.github.com/repos/influxdata/kapacitor/issues/491/comments","statuses_url":"https://api.github.com/repos/influxdata/kapacitor/statuses/0155ec0dcf67597bc7ebb6680227f1291a3f50ef","head":{"label":"yosiat:compiled-stateful-expression","ref":"compiled-stateful-expression","sha":"0155ec0dcf67597bc7ebb6680227f1291a3f50ef","user":{"login":"yosiat","id":306253,"avatar_url":"https://avatars.githubusercontent.com/u/306253?v=3","gravatar_id":"","url":"https://api.github.com/users/yosiat","html_url":"https://github.com/yosiat","followers_url":"https://api.github.com/users/yosiat/followers","following_url":"https://api.github.com/users/yosiat/following{/other_user}","gists_url":"https://api.github.com/users/yosiat/gists{/gist_id}","starred_url":"https://api.github.com/users/yosiat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yosiat/subscriptions","organizations_url":"https://api.github.com/users/yosiat/orgs","repos_url":"https://api.github.com/users/yosiat/repos","events_url":"https://api.github.com/users/yosiat/events{/privacy}","received_events_url":"https://api.github.com/users/yosiat/received_events","type":"User","site_admin":false},"repo":{"id":53682075,"name":"kapacitor","full_name":"yosiat/kapacitor","owner":{"login":"yosiat","id":306253,"avatar_url":"https://avatars.githubusercontent.com/u/306253?v=3","gravatar_id":"","url":"https://api.github.com/users/yosiat","html_url":"https://github.com/yosiat","followers_url":"https://api.github.com/users/yosiat/followers","following_url":"https://api.github.com/users/yosiat/following{/other_user}","gists_url":"https://api.github.com/users/yosiat/gists{/gist_id}","starred_url":"https://api.github.com/users/yosiat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yosiat/subscriptions","organizations_url":"https://api.github.com/users/yosiat/orgs","repos_url":"https://api.github.com/users/yosiat/repos","events_url":"https://api.github.com/users/yosiat/events{/privacy}","received_events_url":"https://api.github.com/users/yosiat/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/yosiat/kapacitor","description":"Open source framework for processing, monitoring, and alerting on time series data","fork":true,"url":"https://api.github.com/repos/yosiat/kapacitor","forks_url":"https://api.github.com/repos/yosiat/kapacitor/forks","keys_url":"https://api.github.com/repos/yosiat/kapacitor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/yosiat/kapacitor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/yosiat/kapacitor/teams","hooks_url":"https://api.github.com/repos/yosiat/kapacitor/hooks","issue_events_url":"https://api.github.com/repos/yosiat/kapacitor/issues/events{/number}","events_url":"https://api.github.com/repos/yosiat/kapacitor/events","assignees_url":"https://api.github.com/repos/yosiat/kapacitor/assignees{/user}","branches_url":"https://api.github.com/repos/yosiat/kapacitor/branches{/branch}","tags_url":"https://api.github.com/repos/yosiat/kapacitor/tags","blobs_url":"https://api.github.com/repos/yosiat/kapacitor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/yosiat/kapacitor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/yosiat/kapacitor/git/refs{/sha}","trees_url":"https://api.github.com/repos/yosiat/kapacitor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/yosiat/kapacitor/statuses/{sha}","languages_url":"https://api.github.com/repos/yosiat/kapacitor/languages","stargazers_url":"https://api.github.com/repos/yosiat/kapacitor/stargazers","contributors_url":"https://api.github.com/repos/yosiat/kapacitor/contributors","subscribers_url":"https://api.github.com/repos/yosiat/kapacitor/subscribers","subscription_url":"https://api.github.com/repos/yosiat/kapacitor/subscription","commits_url":"https://api.github.com/repos/yosiat/kapacitor/commits{/sha}","git_commits_url":"https://api.github.com/repos/yosiat/kapacitor/git/commits{/sha}","comments_url":"https://api.github.com/repos/yosiat/kapacitor/comments{/number}","issue_comment_url":"https://api.github.com/repos/yosiat/kapacitor/issues/comments{/number}","contents_url":"https://api.github.com/repos/yosiat/kapacitor/contents/{+path}","compare_url":"https://api.github.com/repos/yosiat/kapacitor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/yosiat/kapacitor/merges","archive_url":"https://api.github.com/repos/yosiat/kapacitor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/yosiat/kapacitor/downloads","issues_url":"https://api.github.com/repos/yosiat/kapacitor/issues{/number}","pulls_url":"https://api.github.com/repos/yosiat/kapacitor/pulls{/number}","milestones_url":"https://api.github.com/repos/yosiat/kapacitor/milestones{/number}","notifications_url":"https://api.github.com/repos/yosiat/kapacitor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/yosiat/kapacitor/labels{/name}","releases_url":"https://api.github.com/repos/yosiat/kapacitor/releases{/id}","deployments_url":"https://api.github.com/repos/yosiat/kapacitor/deployments","created_at":"2016-03-11T16:49:58Z","updated_at":"2016-03-11T16:50:00Z","pushed_at":"2016-04-28T14:22:26Z","git_url":"git://github.com/yosiat/kapacitor.git","ssh_url":"git@github.com:yosiat/kapacitor.git","clone_url":"https://github.com/yosiat/kapacitor.git","svn_url":"https://github.com/yosiat/kapacitor","homepage":null,"size":9026,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":false,"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":"influxdata:master","ref":"master","sha":"f1a188bd7bd1ee64bff73b71ac9450c9ba516430","user":{"login":"influxdata","id":5713248,"avatar_url":"https://avatars.githubusercontent.com/u/5713248?v=3","gravatar_id":"","url":"https://api.github.com/users/influxdata","html_url":"https://github.com/influxdata","followers_url":"https://api.github.com/users/influxdata/followers","following_url":"https://api.github.com/users/influxdata/following{/other_user}","gists_url":"https://api.github.com/users/influxdata/gists{/gist_id}","starred_url":"https://api.github.com/users/influxdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/influxdata/subscriptions","organizations_url":"https://api.github.com/users/influxdata/orgs","repos_url":"https://api.github.com/users/influxdata/repos","events_url":"https://api.github.com/users/influxdata/events{/privacy}","received_events_url":"https://api.github.com/users/influxdata/received_events","type":"Organization","site_admin":false},"repo":{"id":41681600,"name":"kapacitor","full_name":"influxdata/kapacitor","owner":{"login":"influxdata","id":5713248,"avatar_url":"https://avatars.githubusercontent.com/u/5713248?v=3","gravatar_id":"","url":"https://api.github.com/users/influxdata","html_url":"https://github.com/influxdata","followers_url":"https://api.github.com/users/influxdata/followers","following_url":"https://api.github.com/users/influxdata/following{/other_user}","gists_url":"https://api.github.com/users/influxdata/gists{/gist_id}","starred_url":"https://api.github.com/users/influxdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/influxdata/subscriptions","organizations_url":"https://api.github.com/users/influxdata/orgs","repos_url":"https://api.github.com/users/influxdata/repos","events_url":"https://api.github.com/users/influxdata/events{/privacy}","received_events_url":"https://api.github.com/users/influxdata/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/influxdata/kapacitor","description":"Open source framework for processing, monitoring, and alerting on time series data","fork":false,"url":"https://api.github.com/repos/influxdata/kapacitor","forks_url":"https://api.github.com/repos/influxdata/kapacitor/forks","keys_url":"https://api.github.com/repos/influxdata/kapacitor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/influxdata/kapacitor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/influxdata/kapacitor/teams","hooks_url":"https://api.github.com/repos/influxdata/kapacitor/hooks","issue_events_url":"https://api.github.com/repos/influxdata/kapacitor/issues/events{/number}","events_url":"https://api.github.com/repos/influxdata/kapacitor/events","assignees_url":"https://api.github.com/repos/influxdata/kapacitor/assignees{/user}","branches_url":"https://api.github.com/repos/influxdata/kapacitor/branches{/branch}","tags_url":"https://api.github.com/repos/influxdata/kapacitor/tags","blobs_url":"https://api.github.com/repos/influxdata/kapacitor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/influxdata/kapacitor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/influxdata/kapacitor/git/refs{/sha}","trees_url":"https://api.github.com/repos/influxdata/kapacitor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/influxdata/kapacitor/statuses/{sha}","languages_url":"https://api.github.com/repos/influxdata/kapacitor/languages","stargazers_url":"https://api.github.com/repos/influxdata/kapacitor/stargazers","contributors_url":"https://api.github.com/repos/influxdata/kapacitor/contributors","subscribers_url":"https://api.github.com/repos/influxdata/kapacitor/subscribers","subscription_url":"https://api.github.com/repos/influxdata/kapacitor/subscription","commits_url":"https://api.github.com/repos/influxdata/kapacitor/commits{/sha}","git_commits_url":"https://api.github.com/repos/influxdata/kapacitor/git/commits{/sha}","comments_url":"https://api.github.com/repos/influxdata/kapacitor/comments{/number}","issue_comment_url":"https://api.github.com/repos/influxdata/kapacitor/issues/comments{/number}","contents_url":"https://api.github.com/repos/influxdata/kapacitor/contents/{+path}","compare_url":"https://api.github.com/repos/influxdata/kapacitor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/influxdata/kapacitor/merges","archive_url":"https://api.github.com/repos/influxdata/kapacitor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/influxdata/kapacitor/downloads","issues_url":"https://api.github.com/repos/influxdata/kapacitor/issues{/number}","pulls_url":"https://api.github.com/repos/influxdata/kapacitor/pulls{/number}","milestones_url":"https://api.github.com/repos/influxdata/kapacitor/milestones{/number}","notifications_url":"https://api.github.com/repos/influxdata/kapacitor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/influxdata/kapacitor/labels{/name}","releases_url":"https://api.github.com/repos/influxdata/kapacitor/releases{/id}","deployments_url":"https://api.github.com/repos/influxdata/kapacitor/deployments","created_at":"2015-08-31T14:54:42Z","updated_at":"2016-04-27T06:12:01Z","pushed_at":"2016-04-28T14:22:26Z","git_url":"git://github.com/influxdata/kapacitor.git","ssh_url":"git@github.com:influxdata/kapacitor.git","clone_url":"https://github.com/influxdata/kapacitor.git","svn_url":"https://github.com/influxdata/kapacitor","homepage":null,"size":9435,"stargazers_count":454,"watchers_count":454,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":55,"mirror_url":null,"open_issues_count":72,"forks":55,"open_issues":72,"watchers":454,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/influxdata/kapacitor/pulls/491"},"html":{"href":"https://github.com/influxdata/kapacitor/pull/491"},"issue":{"href":"https://api.github.com/repos/influxdata/kapacitor/issues/491"},"comments":{"href":"https://api.github.com/repos/influxdata/kapacitor/issues/491/comments"},"review_comments":{"href":"https://api.github.com/repos/influxdata/kapacitor/pulls/491/comments"},"review_comment":{"href":"https://api.github.com/repos/influxdata/kapacitor/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/influxdata/kapacitor/pulls/491/commits"},"statuses":{"href":"https://api.github.com/repos/influxdata/kapacitor/statuses/0155ec0dcf67597bc7ebb6680227f1291a3f50ef"}}}}