Repository: incubator-htrace Updated Branches: refs/heads/master 425672669 -> 8b954a76f
HTRACE-53. Move client code into client.go, add unit tests for bin/htrace command and htraced REST API (cmccabe) Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/8b954a76 Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/8b954a76 Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/8b954a76 Branch: refs/heads/master Commit: 8b954a76fd63b23e18823bcc612e9afd36bb3bcd Parents: 4256726 Author: Colin P. Mccabe <[email protected]> Authored: Tue Jan 20 19:29:44 2015 -0800 Committer: Colin P. Mccabe <[email protected]> Committed: Tue Jan 20 19:38:07 2015 -0800 ---------------------------------------------------------------------- .../go/src/org/apache/htrace/client/client.go | 137 ++++++++++++++ .../src/go/src/org/apache/htrace/common/span.go | 14 ++ .../src/go/src/org/apache/htrace/conf/config.go | 28 ++- .../src/go/src/org/apache/htrace/htrace/cmd.go | 185 ++++++++++--------- .../org/apache/htrace/htraced/client_test.go | 127 +++++++++++++ .../src/org/apache/htrace/htraced/datastore.go | 14 +- .../org/apache/htrace/htraced/datastore_test.go | 2 +- .../go/src/org/apache/htrace/htraced/htraced.go | 11 +- .../org/apache/htrace/htraced/mini_htraced.go | 17 ++ .../go/src/org/apache/htrace/htraced/rest.go | 47 +++-- 10 files changed, 475 insertions(+), 107 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/client/client.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/client/client.go b/htrace-core/src/go/src/org/apache/htrace/client/client.go new file mode 100644 index 0000000..fbcdcc6 --- /dev/null +++ b/htrace-core/src/go/src/org/apache/htrace/client/client.go @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package client + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "org/apache/htrace/common" + "org/apache/htrace/conf" +) + +// A golang client for htraced. +// TODO: fancier APIs for streaming spans in the background, optimize TCP stuff + +func NewClient(cnf *conf.Config) (*Client, error) { + hcl := Client{restAddr: cnf.Get(conf.HTRACE_WEB_ADDRESS)} + return &hcl, nil +} + +type Client struct { + // REST address of the htraced server. + restAddr string +} + +// Get the htraced server information. +func (hcl *Client) GetServerInfo() (*common.ServerInfo, error) { + buf, _, err := hcl.makeGetRequest("server/info") + if err != nil { + return nil, err + } + var info common.ServerInfo + err = json.Unmarshal(buf, &info) + if err != nil { + return nil, errors.New(fmt.Sprintf("Error: error unmarshalling response "+ + "body %s: %s", string(buf), err.Error())) + } + return &info, nil +} + +// Get information about a trace span. Returns nil, nil if the span was not found. +func (hcl *Client) FindSpan(sid common.SpanId) (*common.Span, error) { + buf, rc, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x", uint64(sid))) + if err != nil { + if rc == http.StatusNoContent { + return nil, nil + } + return nil, err + } + var span common.Span + err = json.Unmarshal(buf, &span) + if err != nil { + return nil, errors.New(fmt.Sprintf("Error unmarshalling response "+ + "body %s: %s", string(buf), err.Error())) + } + return &span, nil +} + +func (hcl *Client) WriteSpan(span *common.Span) error { + buf, err := json.Marshal(span) + if err != nil { + return err + } + _, _, err = hcl.makeRestRequest("POST", "writeSpans", bytes.NewReader(buf)) + if err != nil { + return err + } + return nil +} + +// Find the child IDs of a given span ID. +// TODO: add offset as well as limit? +func (hcl *Client) FindChildren(sid common.SpanId, lim int) ([]common.SpanId, error) { + buf, _, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x/children?lim=%d", + uint64(sid), lim)) + if err != nil { + return nil, err + } + var spanIds []common.SpanId + err = json.Unmarshal(buf, &spanIds) + if err != nil { + return nil, errors.New(fmt.Sprintf("Error: error unmarshalling response "+ + "body %s: %s", string(buf), err.Error())) + } + return spanIds, nil +} + +func (hcl *Client) makeGetRequest(reqName string) ([]byte, int, error) { + return hcl.makeRestRequest("GET", reqName, nil) +} + +// Make a general JSON REST request. +// Returns the request body, the response code, and the error. +// Note: if the response code is non-zero, the error will also be non-zero. +func (hcl *Client) makeRestRequest(reqType string, reqName string, reqBody io.Reader) ([]byte, int, error) { + url := fmt.Sprintf("http://%s/%s", hcl.restAddr, reqName) + req, err := http.NewRequest(reqType, url, reqBody) + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, -1, errors.New(fmt.Sprintf("Error: error making http request to %s: %s\n", url, + err.Error())) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, resp.StatusCode, + errors.New(fmt.Sprintf("Error: got bad response status from %s: %s\n", url, resp.Status)) + } + var body []byte + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, -1, errors.New(fmt.Sprintf("Error: error reading response body: %s\n", err.Error())) + } + return body, 0, nil +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/common/span.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/common/span.go b/htrace-core/src/go/src/org/apache/htrace/common/span.go index 20d3e45..36e716a 100644 --- a/htrace-core/src/go/src/org/apache/htrace/common/span.go +++ b/htrace-core/src/go/src/org/apache/htrace/common/span.go @@ -57,6 +57,20 @@ func (id SpanId) MarshalJSON() ([]byte, error) { return []byte(`"` + fmt.Sprintf("%016x", uint64(id)) + `"`), nil } +type SpanIdSlice []SpanId + +func (s SpanIdSlice) Len() int { + return len(s) +} + +func (s SpanIdSlice) Less(i, j int) bool { + return s[i] < s[j] +} + +func (s SpanIdSlice) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + const DOUBLE_QUOTE = 0x22 func (id *SpanId) UnmarshalJSON(b []byte) error { http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/conf/config.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/conf/config.go b/htrace-core/src/go/src/org/apache/htrace/conf/config.go index 528d6c1..41e39fa 100644 --- a/htrace-core/src/go/src/org/apache/htrace/conf/config.go +++ b/htrace-core/src/go/src/org/apache/htrace/conf/config.go @@ -42,6 +42,9 @@ import ( // For that reason, it is not necessary for the Get, GetInt, etc. functions to take a default value // argument. // +// Configuration objects are immutable. However, you can make a copy of a configuration which adds +// some changes using Configuration#Clone(). +// type Config struct { settings map[string]string @@ -64,7 +67,7 @@ type Builder struct { // Load a configuration from the application's argv, configuration file, and the standard // defaults. -func LoadApplicationConfig() *Config { +func LoadApplicationConfig(values map[string]string) *Config { reader, err := openFile(CONFIG_FILE_NAME, []string{"."}) if err != nil { log.Fatal("Error opening config file: " + err.Error()) @@ -76,6 +79,9 @@ func LoadApplicationConfig() *Config { } bld.Argv = os.Args[1:] bld.Defaults = DEFAULTS + if values != nil { + bld.Values = values + } var cnf *Config cnf, err = bld.Build() if err != nil { @@ -199,3 +205,23 @@ func (cnf *Config) GetInt64(key string) int64 { } return 0 } + +// Make a deep copy of the given configuration. +// Optionally, you can specify particular key/value pairs to change. +// Example: +// cnf2 := cnf.Copy("my.changed.key", "my.new.value") +func (cnf *Config) Clone(args ...string) *Config { + if len(args)%2 != 0 { + panic("The arguments to Config#copy are key1, value1, " + + "key2, value2, and so on. You must specify an even number of arguments.") + } + ncnf := &Config{defaults: cnf.defaults} + ncnf.settings = make(map[string]string) + for k, v := range cnf.settings { + ncnf.settings[k] = v + } + for i := 0; i < len(args); i += 2 { + ncnf.settings[args[i]] = args[i+1] + } + return ncnf +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go index c7318c1..8539914 100644 --- a/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go +++ b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go @@ -20,14 +20,12 @@ package main import ( - "bytes" + "bufio" "encoding/json" - "errors" "fmt" "github.com/alecthomas/kingpin" "io" - "io/ioutil" - "net/http" + htrace "org/apache/htrace/client" "org/apache/htrace/common" "org/apache/htrace/conf" "os" @@ -36,14 +34,17 @@ import ( var RELEASE_VERSION string var GIT_VERSION string -func main() { - // Load htraced configuration - cnf := conf.LoadApplicationConfig() +const EXIT_SUCCESS = 0 +const EXIT_FAILURE = 1 + +var verbose *bool +func main() { // Parse argv app := kingpin.New("htrace", "The HTrace tracing utility.") addr := app.Flag("addr", "Server address."). - Default(cnf.Get(conf.HTRACE_WEB_ADDRESS)).TCP() + Default(conf.DEFAULTS[conf.HTRACE_WEB_ADDRESS]).TCP() + verbose = app.Flag("verbose", "Verbose.").Default("false").Bool() version := app.Command("version", "Print the version of this program.") serverInfo := app.Command("serverInfo", "Print information retrieved from an htraced server.") findSpan := app.Command("findSpan", "Print information about a trace span with a given ID.") @@ -54,20 +55,46 @@ func main() { "number").Required().Uint64() childLim := findChildren.Flag("lim", "Maximum number of child IDs to print.").Default("20").Int() writeSpans := app.Command("writeSpans", "Write spans to the server in JSON form.") - spanJson := writeSpans.Flag("json", "The JSON span data to write to the server.").Required().String() + spanJson := writeSpans.Flag("json", "The JSON span data to write to the server.").String() + spanFile := writeSpans.Flag("file", + "A file containing JSON span data to write to the server.").String() + cmd := kingpin.MustParse(app.Parse(os.Args[1:])) + + // Load htraced configuration + values := make(map[string]string) + values[conf.HTRACE_WEB_ADDRESS] = (*addr).String() + cnf := conf.LoadApplicationConfig(values) + + // Create HTrace client + hcl, err := htrace.NewClient(cnf) + if err != nil { + fmt.Printf("Failed to create HTrace client: %s\n", err.Error()) + os.Exit(EXIT_FAILURE) + } // Handle operation - switch kingpin.MustParse(app.Parse(os.Args[1:])) { + switch cmd { case version.FullCommand(): os.Exit(printVersion()) case serverInfo.FullCommand(): - os.Exit(printServerInfo((*addr).String())) + os.Exit(printServerInfo(hcl)) case findSpan.FullCommand(): - os.Exit(doFindSpan((*addr).String(), *findSpanId)) + os.Exit(doFindSpan(hcl, common.SpanId(*findSpanId))) case findChildren.FullCommand(): - os.Exit(doFindChildren((*addr).String(), *parentSpanId, *childLim)) + os.Exit(doFindChildren(hcl, common.SpanId(*parentSpanId), *childLim)) case writeSpans.FullCommand(): - os.Exit(doWriteSpans((*addr).String(), *spanJson)) + if *spanJson != "" { + if *spanFile != "" { + fmt.Printf("You must specify either --json or --file, " + + "but not both.\n") + os.Exit(EXIT_FAILURE) + } + os.Exit(doWriteSpanJson(hcl, *spanJson)) + } else if *spanFile != "" { + os.Exit(doWriteSpanJsonFile(hcl, *spanFile)) + } + fmt.Printf("You must specify either --json or --file.\n") + os.Exit(EXIT_FAILURE) } app.UsageErrorf(os.Stderr, "You must supply a command to run.") @@ -76,73 +103,91 @@ func main() { // Print the version of the htrace binary. func printVersion() int { fmt.Printf("Running htrace command version %s.\n", RELEASE_VERSION) - return 0 + return EXIT_SUCCESS } // Print information retrieved from an htraced server via /server/info -func printServerInfo(restAddr string) int { - buf, err := makeGetRequest(restAddr, "server/info") - if err != nil { - fmt.Printf("%s\n", err.Error()) - return 1 - } - var info common.ServerInfo - err = json.Unmarshal(buf, &info) +func printServerInfo(hcl *htrace.Client) int { + info, err := hcl.GetServerInfo() if err != nil { - fmt.Printf("Error: error unmarshalling response body %s: %s\n", - string(buf), err.Error()) - return 1 + fmt.Println(err.Error()) + return EXIT_FAILURE } fmt.Printf("HTraced server version %s (%s)\n", info.ReleaseVersion, info.GitVersion) - return 0 + return EXIT_SUCCESS } // Print information about a trace span. -func doFindSpan(restAddr string, sid uint64) int { - buf, err := makeGetRequest(restAddr, fmt.Sprintf("span/%016x", sid)) +func doFindSpan(hcl *htrace.Client, sid common.SpanId) int { + span, err := hcl.FindSpan(sid) if err != nil { - fmt.Printf("%s\n", err.Error()) - return 1 + fmt.Println(err.Error()) + return EXIT_FAILURE } - var span common.Span - err = json.Unmarshal(buf, &span) - if err != nil { - fmt.Printf("Error: error unmarshalling response body %s: %s\n", - string(buf), err.Error()) - return 1 + if span == nil { + fmt.Printf("Span ID not found.\n") + return EXIT_FAILURE } pbuf, err := json.MarshalIndent(span, "", " ") if err != nil { - fmt.Println("Error: error pretty-printing span to JSON: %s", err.Error()) - return 1 + fmt.Printf("Error: error pretty-printing span to JSON: %s\n", err.Error()) + return EXIT_FAILURE } fmt.Printf("%s\n", string(pbuf)) - return 0 + return EXIT_SUCCESS } -func doWriteSpans(restAddr string, spanJson string) int { - body := []byte(spanJson) - _, err := makeRestRequest("POST", restAddr, "writeSpans", bytes.NewReader(body)) +func doWriteSpanJsonFile(hcl *htrace.Client, spanFile string) int { + file, err := os.Open(spanFile) if err != nil { - fmt.Printf("%s\n", err.Error()) - return 1 + fmt.Printf("Failed to open %s: %s\n", spanFile, err.Error()) + return EXIT_FAILURE } - return 0 + defer file.Close() + in := bufio.NewReader(file) + dec := json.NewDecoder(in) + for { + var span common.Span + if err = dec.Decode(&span); err != nil { + if err == io.EOF { + break + } + fmt.Println("Failed to decode JSON: %s", err.Error()) + return EXIT_FAILURE + } + if *verbose { + fmt.Printf("wrote %s\n", span.ToJson()) + } + if err = hcl.WriteSpan(&span); err != nil { + fmt.Println(err.Error()) + return EXIT_FAILURE + } + } + return EXIT_SUCCESS } -// Find information about the children of a span. -func doFindChildren(restAddr string, sid uint64, lim int) int { - buf, err := makeGetRequest(restAddr, fmt.Sprintf("span/%016x/children?lim=%d", sid, lim)) +func doWriteSpanJson(hcl *htrace.Client, spanJson string) int { + spanBytes := []byte(spanJson) + var span common.Span + err := json.Unmarshal(spanBytes, &span) if err != nil { - fmt.Printf("%s\n", err.Error()) - return 1 + fmt.Printf("Error parsing provided JSON: %s\n", err.Error()) + return EXIT_FAILURE } - var spanIds []common.SpanId - err = json.Unmarshal(buf, &spanIds) + err = hcl.WriteSpan(&span) if err != nil { - fmt.Printf("Error: error unmarshalling response body %s: %s\n", - string(buf), err.Error()) - return 1 + fmt.Println(err.Error()) + return EXIT_FAILURE + } + return EXIT_SUCCESS +} + +// Find information about the children of a span. +func doFindChildren(hcl *htrace.Client, sid common.SpanId, lim int) int { + spanIds, err := hcl.FindChildren(sid, lim) + if err != nil { + fmt.Printf("%s\n", err.Error()) + return EXIT_FAILURE } pbuf, err := json.MarshalIndent(spanIds, "", " ") if err != nil { @@ -152,31 +197,3 @@ func doFindChildren(restAddr string, sid uint64, lim int) int { fmt.Printf("%s\n", string(pbuf)) return 0 } - -func makeGetRequest(restAddr string, reqName string) ([]byte, error) { - return makeRestRequest("GET", restAddr, reqName, nil) -} - -// Print information retrieved from an htraced server via /serverInfo -func makeRestRequest(reqType string, restAddr string, reqName string, - reqBody io.Reader) ([]byte, error) { - url := fmt.Sprintf("http://%s/%s", restAddr, reqName) - req, err := http.NewRequest(reqType, url, reqBody) - req.Header.Set("Content-Type", "application/json") - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, errors.New(fmt.Sprintf("Error: error making http request to %s: %s\n", url, - err.Error())) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, errors.New(fmt.Sprintf("Error: got bad response status from %s: %s\n", url, resp.Status)) - } - var body []byte - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - return nil, errors.New(fmt.Sprintf("Error: error reading response body: %s\n", err.Error())) - } - return body, nil -} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go b/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go new file mode 100644 index 0000000..15fbe89 --- /dev/null +++ b/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package main + +import ( + "math/rand" + htrace "org/apache/htrace/client" + "org/apache/htrace/common" + "org/apache/htrace/test" + "testing" +) + +func TestClientGetServerInfo(t *testing.T) { + htraceBld := &MiniHTracedBuilder{Name: "TestClientGetServerInfo", NumDataDirs: 1} + ht, err := htraceBld.Build() + if err != nil { + t.Fatalf("failed to create datastore: %s", err.Error()) + } + defer ht.Close() + var hcl *htrace.Client + hcl, err = htrace.NewClient(ht.ClientConf()) + if err != nil { + t.Fatalf("failed to create client: %s", err.Error()) + } + _, err = hcl.GetServerInfo() + if err != nil { + t.Fatalf("failed to call GetServerInfo: %s", err.Error()) + } +} + +func TestClientOperations(t *testing.T) { + htraceBld := &MiniHTracedBuilder{Name: "TestClientOperations", NumDataDirs: 2} + ht, err := htraceBld.Build() + if err != nil { + t.Fatalf("failed to create datastore: %s", err.Error()) + } + defer ht.Close() + var hcl *htrace.Client + hcl, err = htrace.NewClient(ht.ClientConf()) + if err != nil { + t.Fatalf("failed to create client: %s", err.Error()) + } + + // Create some random trace spans. + NUM_TEST_SPANS := 30 + rnd := rand.New(rand.NewSource(2)) + allSpans := make([]*common.Span, NUM_TEST_SPANS) + allSpans[0] = test.NewRandomSpan(rnd, allSpans[0:0]) + for i := 1; i < NUM_TEST_SPANS; i++ { + allSpans[i] = test.NewRandomSpan(rnd, allSpans[1:i]) + } + allSpans[1].SpanData.Parents = []common.SpanId{common.SpanId(allSpans[0].Id)} + + // Write half of the spans to htraced via the client. + for i := 0; i < NUM_TEST_SPANS/2; i++ { + if err := hcl.WriteSpan(allSpans[i]); err != nil { + t.Fatalf("WriteSpan(%d) failed: %s\n", i, err.Error()) + } + } + + // Look up the first half of the spans. They should be found. + var span *common.Span + for i := 0; i < NUM_TEST_SPANS/2; i++ { + span, err = hcl.FindSpan(allSpans[i].Id) + if err != nil { + t.Fatalf("FindSpan(%d) failed: %s\n", i, err.Error()) + } + common.ExpectSpansEqual(t, allSpans[i], span) + } + + // Look up the second half of the spans. They should not be found. + for i := NUM_TEST_SPANS / 2; i < NUM_TEST_SPANS; i++ { + span, err = hcl.FindSpan(allSpans[i].Id) + if err != nil { + t.Fatalf("FindSpan(%d) failed: %s\n", i, err.Error()) + } + if span != nil { + t.Fatalf("Unexpectedly found a span we never write to "+ + "the server: FindSpan(%d) succeeded\n", i) + } + } + + // Test FindChildren + childSpan := allSpans[1] + parentId := childSpan.Parents[0] + var children []common.SpanId + children, err = hcl.FindChildren(parentId, 1) + if err != nil { + t.Fatalf("FindChildren(%s) failed: %s\n", parentId, err.Error()) + } + if len(children) != 1 { + t.Fatalf("FindChildren(%s) returned an invalid number of "+ + "children: expected %d, got %d\n", parentId, 1, len(children)) + } + if children[0] != childSpan.Id { + t.Fatalf("FindChildren(%s) returned an invalid child id: expected %s, "+ + " got %s\n", parentId, childSpan.Id, children[0]) + } + + // Test FindChildren on a span that has no children + childlessSpan := allSpans[NUM_TEST_SPANS/2] + children, err = hcl.FindChildren(childlessSpan.Id, 10) + if err != nil { + t.Fatalf("FindChildren(%d) failed: %s\n", childlessSpan.Id, err.Error()) + } + if len(children) != 0 { + t.Fatalf("FindChildren(%d) returned an invalid number of "+ + "children: expected %d, got %d\n", childlessSpan.Id, 0, len(children)) + } +} http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go index b43d4ce..2137063 100644 --- a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go +++ b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go @@ -210,7 +210,7 @@ func (shd *shard) writeSpan(span *common.Span) error { return nil } -func (shd *shard) FindChildren(sid int64, childIds []int64, lim int32) ([]int64, int32, error) { +func (shd *shard) FindChildren(sid int64, childIds []common.SpanId, lim int32) ([]common.SpanId, int32, error) { searchKey := makeKey('p', sid) iter := shd.ldb.NewIterator(shd.store.readOpts) defer iter.Close() @@ -226,7 +226,7 @@ func (shd *shard) FindChildren(sid int64, childIds []int64, lim int32) ([]int64, if !bytes.HasPrefix(key, searchKey) { break } - id := keyToInt(key[9:]) + id := common.SpanId(keyToInt(key[9:])) childIds = append(childIds, id) lim-- iter.Next() @@ -390,7 +390,7 @@ func (shd *shard) FindSpan(sid int64) *common.Span { if strings.Index(err.Error(), "NotFound:") != -1 { return nil } - log.Printf("Shard(%s): FindSpan(%d) error: %s\n", + log.Printf("Shard(%s): FindSpan(%016x) error: %s\n", shd.path, sid, err.Error()) return nil } @@ -399,7 +399,7 @@ func (shd *shard) FindSpan(sid int64) *common.Span { data := common.SpanData{} err = decoder.Decode(&data) if err != nil { - log.Printf("Shard(%s): FindSpan(%d) decode error: %s\n", + log.Printf("Shard(%s): FindSpan(%016x) decode error: %s\n", shd.path, sid, err.Error()) return nil } @@ -412,8 +412,8 @@ func (shd *shard) FindSpan(sid int64) *common.Span { } // Find the children of a given span id. -func (store *dataStore) FindChildren(sid int64, lim int32) []int64 { - childIds := make([]int64, 0) +func (store *dataStore) FindChildren(sid int64, lim int32) []common.SpanId { + childIds := make([]common.SpanId, 0) var err error startIdx := store.getShardIndex(sid) @@ -426,7 +426,7 @@ func (store *dataStore) FindChildren(sid int64, lim int32) []int64 { shd := store.shards[idx] childIds, lim, err = shd.FindChildren(sid, childIds, lim) if err != nil { - log.Printf("Shard(%s): FindChildren(%d) error: %s\n", + log.Printf("Shard(%s): FindChildren(%016x) error: %s\n", shd.path, sid, err.Error()) } idx++ http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go index f037145..f0449fe 100644 --- a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go +++ b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go @@ -107,7 +107,7 @@ func TestDatastoreWriteAndRead(t *testing.T) { if len(children) != 2 { t.Fatalf("expected 2 children, but got %d\n", len(children)) } - sort.Sort(common.Int64Slice(children)) + sort.Sort(common.SpanIdSlice(children)) if children[0] != 2 { t.Fatal() } http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go b/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go index 403b1f9..d444a02 100644 --- a/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go +++ b/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go @@ -22,16 +22,23 @@ package main import ( "log" "org/apache/htrace/conf" + "time" ) var RELEASE_VERSION string var GIT_VERSION string func main() { - cnf := conf.LoadApplicationConfig() + cnf := conf.LoadApplicationConfig(nil) store, err := CreateDataStore(cnf, nil) if err != nil { log.Fatalf("Error creating datastore: %s\n", err.Error()) } - startRestServer(cnf, store) + _, err = CreateRestServer(cnf, store) + if err != nil { + log.Fatalf("Error creating REST server: %s\n", err.Error()) + } + for { + time.Sleep(time.Duration(10) * time.Hour) + } } http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go b/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go index 43d7e23..fd8c2a7 100644 --- a/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go +++ b/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go @@ -59,11 +59,13 @@ type MiniHTraced struct { Cnf *conf.Config DataDirs []string Store *dataStore + Rsv *RestServer } func (bld *MiniHTracedBuilder) Build() (*MiniHTraced, error) { var err error var store *dataStore + var rsv *RestServer if bld.Name == "" { bld.Name = "HTraceTest" } @@ -84,6 +86,9 @@ func (bld *MiniHTracedBuilder) Build() (*MiniHTraced, error) { os.RemoveAll(dataDirs[idx]) } } + if rsv != nil { + rsv.Close() + } } }() for idx := range dataDirs { @@ -94,6 +99,7 @@ func (bld *MiniHTracedBuilder) Build() (*MiniHTraced, error) { } } bld.Cnf[conf.HTRACE_DATA_STORE_DIRECTORIES] = strings.Join(dataDirs, conf.PATH_LIST_SEP) + bld.Cnf[conf.HTRACE_WEB_ADDRESS] = ":0" // use a random port for the REST server cnfBld := conf.Builder{Values: bld.Cnf, Defaults: conf.DEFAULTS} cnf, err := cnfBld.Build() if err != nil { @@ -103,14 +109,25 @@ func (bld *MiniHTracedBuilder) Build() (*MiniHTraced, error) { if err != nil { return nil, err } + rsv, err = CreateRestServer(cnf, store) + if err != nil { + return nil, err + } return &MiniHTraced{ Cnf: cnf, DataDirs: dataDirs, Store: store, + Rsv: rsv, }, nil } +// Return a Config object that clients can use to connect to this MiniHTraceD. +func (ht *MiniHTraced) ClientConf() *conf.Config { + return ht.Cnf.Clone(conf.HTRACE_WEB_ADDRESS, ht.Rsv.Addr().String()) +} + func (ht *MiniHTraced) Close() { + ht.Rsv.Close() ht.Store.Close() for idx := range ht.DataDirs { os.RemoveAll(ht.DataDirs[idx]) http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/8b954a76/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go ---------------------------------------------------------------------- diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go index 6db1b7d..d175f4e 100644 --- a/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go +++ b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go @@ -26,6 +26,7 @@ import ( "io" "log" "mime" + "net" "net/http" "org/apache/htrace/common" "org/apache/htrace/conf" @@ -41,9 +42,8 @@ func setResponseHeaders(hdr http.Header) { } // Write a JSON error response. -func writeError(w http.ResponseWriter, errCode int, fstr string, args ...interface{}) { - str := fmt.Sprintf(fstr, args) - str = strings.Replace(str, `"`, `'`, -1) +func writeError(w http.ResponseWriter, errCode int, errStr string) { + str := strings.Replace(errStr, `"`, `'`, -1) log.Println(str) w.WriteHeader(errCode) w.Write([]byte(`{ "error" : "` + str + `"}`)) @@ -59,7 +59,7 @@ func (handler *serverInfoHandler) ServeHTTP(w http.ResponseWriter, req *http.Req buf, err := json.Marshal(&version) if err != nil { writeError(w, http.StatusInternalServerError, - "error marshalling ServerInfo: %s\n", err.Error()) + fmt.Sprintf("error marshalling ServerInfo: %s\n", err.Error())) return } w.Write(buf) @@ -72,8 +72,8 @@ type dataStoreHandler struct { func (hand *dataStoreHandler) parse64(w http.ResponseWriter, str string) (int64, bool) { val, err := strconv.ParseUint(str, 16, 64) if err != nil { - writeError(w, http.StatusBadRequest, "Failed to parse span ID %s: %s", - str, err.Error()) + writeError(w, http.StatusBadRequest, + fmt.Sprintf("Failed to parse span ID %s: %s", str, err.Error())) w.Write([]byte("Error parsing : " + err.Error())) return -1, false } @@ -84,12 +84,13 @@ func (hand *dataStoreHandler) getReqField32(fieldName string, w http.ResponseWri req *http.Request) (int32, bool) { str := req.FormValue(fieldName) if str == "" { - writeError(w, http.StatusBadRequest, "No %s specified.", fieldName) + writeError(w, http.StatusBadRequest, fmt.Sprintf("No %s specified.", fieldName)) return -1, false } val, err := strconv.ParseUint(str, 16, 32) if err != nil { - writeError(w, http.StatusBadRequest, "Error parsing %s: %s.", fieldName, err.Error()) + writeError(w, http.StatusBadRequest, + fmt.Sprintf("Error parsing %s: %s.", fieldName, err.Error())) return -1, false } return int32(val), true @@ -110,7 +111,8 @@ func (hand *findSidHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) } span := hand.store.FindSpan(sid) if span == nil { - writeError(w, http.StatusNoContent, "No spans were specified.") + writeError(w, http.StatusNoContent, fmt.Sprintf("No such span as %s", + common.SpanId(sid))) return } w.Write(span.ToJson()) @@ -155,7 +157,8 @@ func (hand *writeSpansHandler) ServeHTTP(w http.ResponseWriter, req *http.Reques err := dec.Decode(&span) if err != nil { if err != io.EOF { - writeError(w, http.StatusBadRequest, "Error parsing spans: %s", err.Error()) + writeError(w, http.StatusBadRequest, + fmt.Sprintf("Error parsing spans: %s", err.Error())) return } break @@ -189,7 +192,17 @@ func (hand *defaultServeHandler) ServeHTTP(w http.ResponseWriter, req *http.Requ w.Write([]byte(rsc)) } -func startRestServer(cnf *conf.Config, store *dataStore) { +type RestServer struct { + listener net.Listener +} + +func CreateRestServer(cnf *conf.Config, store *dataStore) (*RestServer, error) { + var err error + rsv := &RestServer{} + rsv.listener, err = net.Listen("tcp", cnf.Get(conf.HTRACE_WEB_ADDRESS)) + if err != nil { + return nil, err + } r := mux.NewRouter().StrictSlash(false) // Default Handler. This will serve requests for static requests. @@ -207,6 +220,16 @@ func startRestServer(cnf *conf.Config, store *dataStore) { findChildrenH := &findChildrenHandler{dataStoreHandler: dataStoreHandler{store: store}} span.Handle("/{id}/children", findChildrenH).Methods("GET") - http.ListenAndServe(cnf.Get(conf.HTRACE_WEB_ADDRESS), r) + go http.Serve(rsv.listener, r) + log.Println("Started REST server...") + return rsv, nil +} + +func (rsv *RestServer) Addr() net.Addr { + return rsv.listener.Addr() +} + +func (rsv *RestServer) Close() { + rsv.listener.Close() }
