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()
 }

Reply via email to