HTRACE-53. Move client code into client.go, add unit tests for bin/htrace command and htraced REST API (cmccabe)
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
+}
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 @@
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 {
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 @@
// 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 @@
// 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 @@
}
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 @@
}
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
+}
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 @@
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 @@
"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 @@
// 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")
+func printServerInfo(hcl *htrace.Client) int {
+ info, err := hcl.GetServerInfo()
if err != nil {
- fmt.Printf("%s\n", err.Error())
- return 1
- }
- var info common.ServerInfo
- err = json.Unmarshal(buf, &info)
- 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
+}
+
+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("Error parsing provided JSON: %s\n", err.Error())
+ return EXIT_FAILURE
+ }
+ err = hcl.WriteSpan(&span)
+ if 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 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 1
- }
- var spanIds []common.SpanId
- err = json.Unmarshal(buf, &spanIds)
- if err != nil {
- fmt.Printf("Error: error unmarshalling response body %s: %s\n",
- string(buf), err.Error())
- return 1
+ return EXIT_FAILURE
}
pbuf, err := json.MarshalIndent(spanIds, "", " ")
if err != nil {
@@ -152,31 +197,3 @@
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
-}
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))
+ }
+}
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 @@
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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++
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 @@
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()
}
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 @@
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)
+ }
}
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 @@
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 @@
os.RemoveAll(dataDirs[idx])
}
}
+ if rsv != nil {
+ rsv.Close()
+ }
}
}()
for idx := range dataDirs {
@@ -94,6 +99,7 @@
}
}
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 @@
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])
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 @@
"io"
"log"
"mime"
+ "net"
"net/http"
"org/apache/htrace/common"
"org/apache/htrace/conf"
@@ -41,9 +42,8 @@
}
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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()
}