From a51e479cc29b3636735f60ffba15767f0fb6b598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AA=E0=AE=BE=E0=AE=B2=E0=AE=BE=E0=AE=9C=E0=AE=BF?= <balaji@dgraph.io> Date: Fri, 7 Aug 2020 12:05:14 +0530 Subject: [PATCH 1/8] add query cost to the header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: பாலாஜி <balaji@dgraph.io> --- dgraph/cmd/alpha/http.go | 5 ++ dgraph/cmd/alpha/http_test.go | 118 ++++++++++++++++++++++++++++++++++ edgraph/server.go | 5 +- x/x.go | 1 + 4 files changed, 128 insertions(+), 1 deletion(-) diff --git a/dgraph/cmd/alpha/http.go b/dgraph/cmd/alpha/http.go index 2e64e6f14de..263a4b8277e 100644 --- a/dgraph/cmd/alpha/http.go +++ b/dgraph/cmd/alpha/http.go @@ -21,6 +21,7 @@ import ( "compress/gzip" "context" "encoding/json" + "fmt" "io" "io/ioutil" "net/http" @@ -239,6 +240,8 @@ func queryHandler(w http.ResponseWriter, r *http.Request) { x.SetStatusWithData(w, x.ErrorInvalidRequest, err.Error()) return } + // Add cost to the header. + w.Header().Set(x.DgraphCostHeader, fmt.Sprint(resp.Metrics.NumUids["_total"])) e := query.Extensions{ Txn: resp.Txn, @@ -391,6 +394,8 @@ func mutationHandler(w http.ResponseWriter, r *http.Request) { x.SetStatusWithData(w, x.ErrorInvalidRequest, err.Error()) return } + // Add cost to the header. + w.Header().Set(x.DgraphCostHeader, fmt.Sprint(resp.Metrics.NumUids["_total"])) resp.Latency.ParsingNs = uint64(parseEnd.Sub(parseStart).Nanoseconds()) e := query.Extensions{ diff --git a/dgraph/cmd/alpha/http_test.go b/dgraph/cmd/alpha/http_test.go index b39446a05d3..7b1400338d8 100644 --- a/dgraph/cmd/alpha/http_test.go +++ b/dgraph/cmd/alpha/http_test.go @@ -196,6 +196,37 @@ func queryWithTs(queryText, contentType, debug string, ts uint64) (string, uint6 return string(output), startTs, err } +func queryWithTsForCost(queryText, contentType, debug string, ts uint64) (string, + uint64, string, error) { + params := make([]string, 0, 2) + if debug != "" { + params = append(params, "debug="+debug) + } + if ts != 0 { + params = append(params, fmt.Sprintf("startTs=%v", strconv.FormatUint(ts, 10))) + } + url := addr + "/query?" + strings.Join(params, "&") + + _, body, cost, err := runWithRetriesForCost("POST", contentType, url, queryText) + if err != nil { + return "", 0, cost, err + } + + var r res + if err := json.Unmarshal(body, &r); err != nil { + return "", 0, cost, err + } + startTs := r.Extensions.Txn.StartTs + + // Remove the extensions. + r2 := res{ + Data: r.Data, + } + output, err := json.Marshal(r2) + + return string(output), startTs, cost, err +} + type mutationResponse struct { keys []string preds []string @@ -314,6 +345,61 @@ func runRequest(req *http.Request) (*x.QueryResWithData, []byte, error) { return qr, body, nil } +func runWithRetriesForCost(method, contentType, url string, body string) ( + *x.QueryResWithData, []byte, string, error) { + + req, err := createRequest(method, contentType, url, body) + if err != nil { + return nil, nil, "", err + } + + qr, respBody, cost, err := runRequestForCost(req) + if err != nil && strings.Contains(err.Error(), "Token is expired") { + grootAccessJwt, grootRefreshJwt, err = testutil.HttpLogin(&testutil.LoginParams{ + Endpoint: addr + "/admin", + RefreshJwt: grootRefreshJwt, + }) + if err != nil { + return nil, nil, "", err + } + + // create a new request since the previous request would have been closed upon the err + retryReq, err := createRequest(method, contentType, url, body) + if err != nil { + return nil, nil, "", err + } + + return runRequestForCost(retryReq) + } + return qr, respBody, cost, err +} + +// attach the grootAccessJWT to the request and sends the http request +func runRequestForCost(req *http.Request) (*x.QueryResWithData, []byte, string, error) { + client := &http.Client{} + req.Header.Set("X-Dgraph-AccessToken", grootAccessJwt) + resp, err := client.Do(req) + if err != nil { + return nil, nil, "", err + } + if status := resp.StatusCode; status != http.StatusOK { + return nil, nil, "", errors.Errorf("Unexpected status code: %v", status) + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, nil, "", errors.Errorf("unable to read from body: %v", err) + } + + qr := new(x.QueryResWithData) + json.Unmarshal(body, qr) // Don't check error. + if len(qr.Errors) > 0 { + return nil, nil, "", errors.New(qr.Errors[0].Message) + } + return qr, body, resp.Header.Get(x.DgraphCostHeader), nil +} + func commitWithTs(keys, preds []string, ts uint64) error { url := addr + "/commit" if ts != 0 { @@ -452,6 +538,38 @@ func TestTransactionBasicNoPreds(t *testing.T) { require.NoError(t, err) require.Equal(t, `{"data":{"balances":[{"name":"Bob","balance":"110"}]}}`, data) } +func TestTransactionForCost(t *testing.T) { + require.NoError(t, dropAll()) + require.NoError(t, alterSchema(`name: string @index(term) .`)) + + q1 := ` + { + balances(func: anyofterms(name, "Alice Bob")) { + name + balance + } + } + ` + _, _, err := queryWithTs(q1, "application/graphql+-", "", 0) + require.NoError(t, err) + + m1 := ` + { + set { + _:alice <name> "Bob" . + _:alice <balance> "110" . + _:bob <balance> "60" . + } + } + ` + + _, err = mutationWithTs(m1, "application/rdf", false, true, 0) + require.NoError(t, err) + + _, _, cost, err := queryWithTsForCost(q1, "application/graphql+-", "", 0) + require.NoError(t, err) + fmt.Println(cost) +} func TestTransactionBasicOldCommitFormat(t *testing.T) { require.NoError(t, dropAll()) diff --git a/edgraph/server.go b/edgraph/server.go index b46494c89bc..ec2e04d756c 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "math" "net" "sort" @@ -36,6 +37,7 @@ import ( "go.opencensus.io/tag" "go.opencensus.io/trace" otrace "go.opencensus.io/trace" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" @@ -997,7 +999,8 @@ func (s *Server) doQuery(ctx context.Context, req *api.Request, doAuth AuthMode) EncodingNs: uint64(l.Json.Nanoseconds()), TotalNs: uint64((time.Since(l.Start)).Nanoseconds()), } - + md := metadata.Pairs(x.DgraphCostHeader, fmt.Sprint(resp.Metrics.NumUids["_total"])) + grpc.SendHeader(ctx, md) return resp, nil } diff --git a/x/x.go b/x/x.go index 53db9638dee..7646613aa26 100644 --- a/x/x.go +++ b/x/x.go @@ -146,6 +146,7 @@ const ( AccessControlAllowedHeaders = "X-Dgraph-AccessToken, " + "Content-Type, Content-Length, Accept-Encoding, Cache-Control, " + "X-CSRF-Token, X-Auth-Token, X-Requested-With" + DgraphCostHeader = "X-Dgraph-Cost" // GraphqlPredicates is the json representation of the predicate reserved for graphql system. GraphqlPredicates = ` From 8dec1ffabff9c8388c89200e87353e98d128302a Mon Sep 17 00:00:00 2001 From: Balaji <rbalajis25@gmail.com> Date: Fri, 7 Aug 2020 07:03:20 +0000 Subject: [PATCH 2/8] fix panic Signed-off-by: Balaji <rbalajis25@gmail.com> --- dgraph/cmd/alpha/http_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dgraph/cmd/alpha/http_test.go b/dgraph/cmd/alpha/http_test.go index 7b1400338d8..7bd3e837dce 100644 --- a/dgraph/cmd/alpha/http_test.go +++ b/dgraph/cmd/alpha/http_test.go @@ -568,7 +568,7 @@ func TestTransactionForCost(t *testing.T) { _, _, cost, err := queryWithTsForCost(q1, "application/graphql+-", "", 0) require.NoError(t, err) - fmt.Println(cost) + require.Equal(t, "2", cost) } func TestTransactionBasicOldCommitFormat(t *testing.T) { From 622a4c763be70e0966ea5a23e4cffb3a56aca8c9 Mon Sep 17 00:00:00 2001 From: Balaji <rbalajis25@gmail.com> Date: Fri, 7 Aug 2020 07:03:42 +0000 Subject: [PATCH 3/8] fix minor Signed-off-by: Balaji <rbalajis25@gmail.com> --- edgraph/server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/edgraph/server.go b/edgraph/server.go index ec2e04d756c..8a4c1cdea8f 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -1007,6 +1007,10 @@ func (s *Server) doQuery(ctx context.Context, req *api.Request, doAuth AuthMode) func processQuery(ctx context.Context, qc *queryContext) (*api.Response, error) { resp := &api.Response{} if len(qc.req.Query) == 0 { + // No query, so make the query cost 0. + resp.Metrics = &api.Metrics{ + NumUids: map[string]uint64{"_total": 0}, + } return resp, nil } if ctx.Err() != nil { From e23efa506b2d4f75a60658147a5f2722324adaf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AA=E0=AE=BE=E0=AE=B2=E0=AE=BE=E0=AE=9C=E0=AE=BF?= <balaji@dgraph.io> Date: Fri, 7 Aug 2020 14:17:22 +0530 Subject: [PATCH 4/8] change header name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: பாலாஜி <balaji@dgraph.io> --- x/x.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/x.go b/x/x.go index 7646613aa26..0bdb09cb94d 100644 --- a/x/x.go +++ b/x/x.go @@ -146,7 +146,7 @@ const ( AccessControlAllowedHeaders = "X-Dgraph-AccessToken, " + "Content-Type, Content-Length, Accept-Encoding, Cache-Control, " + "X-CSRF-Token, X-Auth-Token, X-Requested-With" - DgraphCostHeader = "X-Dgraph-Cost" + DgraphCostHeader = "numUids" // GraphqlPredicates is the json representation of the predicate reserved for graphql system. GraphqlPredicates = ` From d184b13e1de491b952b0468d3f9340b3c1ed37c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AA=E0=AE=BE=E0=AE=B2=E0=AE=BE=E0=AE=9C=E0=AE=BF?= <balaji@dgraph.io> Date: Fri, 7 Aug 2020 14:25:45 +0530 Subject: [PATCH 5/8] return resp instead of cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: பாலாஜி <balaji@dgraph.io> --- dgraph/cmd/alpha/http_test.go | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/dgraph/cmd/alpha/http_test.go b/dgraph/cmd/alpha/http_test.go index 7bd3e837dce..22f1856ce47 100644 --- a/dgraph/cmd/alpha/http_test.go +++ b/dgraph/cmd/alpha/http_test.go @@ -196,8 +196,8 @@ func queryWithTs(queryText, contentType, debug string, ts uint64) (string, uint6 return string(output), startTs, err } -func queryWithTsForCost(queryText, contentType, debug string, ts uint64) (string, - uint64, string, error) { +func queryWithTsForResp(queryText, contentType, debug string, ts uint64) (string, + uint64, *http.Response, error) { params := make([]string, 0, 2) if debug != "" { params = append(params, "debug="+debug) @@ -345,59 +345,59 @@ func runRequest(req *http.Request) (*x.QueryResWithData, []byte, error) { return qr, body, nil } -func runWithRetriesForCost(method, contentType, url string, body string) ( - *x.QueryResWithData, []byte, string, error) { +func runWithRetriesForResp(method, contentType, url string, body string) ( + *x.QueryResWithData, []byte, *http.Response, error) { req, err := createRequest(method, contentType, url, body) if err != nil { - return nil, nil, "", err + return nil, nil, nil, err } - qr, respBody, cost, err := runRequestForCost(req) + qr, respBody, resp, err := runRequestForResp(req) if err != nil && strings.Contains(err.Error(), "Token is expired") { grootAccessJwt, grootRefreshJwt, err = testutil.HttpLogin(&testutil.LoginParams{ Endpoint: addr + "/admin", RefreshJwt: grootRefreshJwt, }) if err != nil { - return nil, nil, "", err + return nil, nil, nil, err } // create a new request since the previous request would have been closed upon the err retryReq, err := createRequest(method, contentType, url, body) if err != nil { - return nil, nil, "", err + return nil, nil, resp, err } - return runRequestForCost(retryReq) + return runRequestForResp(retryReq) } - return qr, respBody, cost, err + return qr, respBody, resp, err } // attach the grootAccessJWT to the request and sends the http request -func runRequestForCost(req *http.Request) (*x.QueryResWithData, []byte, string, error) { +func runRequestForResp(req *http.Request) (*x.QueryResWithData, []byte, *http.Response, error) { client := &http.Client{} req.Header.Set("X-Dgraph-AccessToken", grootAccessJwt) resp, err := client.Do(req) if err != nil { - return nil, nil, "", err + return nil, nil, resp, err } if status := resp.StatusCode; status != http.StatusOK { - return nil, nil, "", errors.Errorf("Unexpected status code: %v", status) + return nil, nil, resp, errors.Errorf("Unexpected status code: %v", status) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, nil, "", errors.Errorf("unable to read from body: %v", err) + return nil, nil, resp, errors.Errorf("unable to read from body: %v", err) } qr := new(x.QueryResWithData) json.Unmarshal(body, qr) // Don't check error. if len(qr.Errors) > 0 { - return nil, nil, "", errors.New(qr.Errors[0].Message) + return nil, nil, resp, errors.New(qr.Errors[0].Message) } - return qr, body, resp.Header.Get(x.DgraphCostHeader), nil + return qr, body, resp, nil } func commitWithTs(keys, preds []string, ts uint64) error { @@ -566,9 +566,9 @@ func TestTransactionForCost(t *testing.T) { _, err = mutationWithTs(m1, "application/rdf", false, true, 0) require.NoError(t, err) - _, _, cost, err := queryWithTsForCost(q1, "application/graphql+-", "", 0) + _, _, resp, err := queryWithTsForResp(q1, "application/graphql+-", "", 0) require.NoError(t, err) - require.Equal(t, "2", cost) + require.Equal(t, "2", resp.Header.Get(x.DgraphCostHeader)) } func TestTransactionBasicOldCommitFormat(t *testing.T) { From 78e9b6b1fe902f9bec28c7a303c7d8b723e16d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AA=E0=AE=BE=E0=AE=B2=E0=AE=BE=E0=AE=9C=E0=AE=BF?= <balaji@dgraph.io> Date: Fri, 7 Aug 2020 14:59:03 +0530 Subject: [PATCH 6/8] minor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: பாலாஜி <balaji@dgraph.io> --- dgraph/cmd/alpha/http_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dgraph/cmd/alpha/http_test.go b/dgraph/cmd/alpha/http_test.go index 22f1856ce47..e49d2fa2c6d 100644 --- a/dgraph/cmd/alpha/http_test.go +++ b/dgraph/cmd/alpha/http_test.go @@ -207,14 +207,14 @@ func queryWithTsForResp(queryText, contentType, debug string, ts uint64) (string } url := addr + "/query?" + strings.Join(params, "&") - _, body, cost, err := runWithRetriesForCost("POST", contentType, url, queryText) + _, body, resp, err := runWithRetriesForResp("POST", contentType, url, queryText) if err != nil { - return "", 0, cost, err + return "", 0, resp, err } var r res if err := json.Unmarshal(body, &r); err != nil { - return "", 0, cost, err + return "", 0, resp, err } startTs := r.Extensions.Txn.StartTs @@ -224,7 +224,7 @@ func queryWithTsForResp(queryText, contentType, debug string, ts uint64) (string } output, err := json.Marshal(r2) - return string(output), startTs, cost, err + return string(output), startTs, resp, err } type mutationResponse struct { From d0fac7477158b5b0963de859b39b617872b9e3ee Mon Sep 17 00:00:00 2001 From: Tejas Dinkar <tejas@gja.in> Date: Fri, 7 Aug 2020 16:29:02 +0530 Subject: [PATCH 7/8] Update x.go --- x/x.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/x.go b/x/x.go index 0bdb09cb94d..c54d198590e 100644 --- a/x/x.go +++ b/x/x.go @@ -146,7 +146,7 @@ const ( AccessControlAllowedHeaders = "X-Dgraph-AccessToken, " + "Content-Type, Content-Length, Accept-Encoding, Cache-Control, " + "X-CSRF-Token, X-Auth-Token, X-Requested-With" - DgraphCostHeader = "numUids" + DgraphCostHeader = "Dgraph-TouchedUids" // GraphqlPredicates is the json representation of the predicate reserved for graphql system. GraphqlPredicates = ` From f15546357760732f0b4efd4c069a951509ea44f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AA=E0=AE=BE=E0=AE=B2=E0=AE=BE=E0=AE=9C=E0=AE=BF?= <balaji@dgraph.io> Date: Sun, 9 Aug 2020 17:48:36 +0530 Subject: [PATCH 8/8] add comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: பாலாஜி <balaji@dgraph.io> --- dgraph/cmd/alpha/http_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dgraph/cmd/alpha/http_test.go b/dgraph/cmd/alpha/http_test.go index e49d2fa2c6d..8d5316fbd9e 100644 --- a/dgraph/cmd/alpha/http_test.go +++ b/dgraph/cmd/alpha/http_test.go @@ -196,6 +196,7 @@ func queryWithTs(queryText, contentType, debug string, ts uint64) (string, uint6 return string(output), startTs, err } +// queryWithTsForResp query the dgraph and returns it's http response and result. func queryWithTsForResp(queryText, contentType, debug string, ts uint64) (string, uint64, *http.Response, error) { params := make([]string, 0, 2)