You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by cm...@apache.org on 2014/12/06 01:50:38 UTC
incubator-htrace git commit: HTRACE-9: Add standalone htraced server
Repository: incubator-htrace
Updated Branches:
refs/heads/master fc7bf2383 -> 192595391
HTRACE-9: Add standalone htraced server
Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/19259539
Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/19259539
Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/19259539
Branch: refs/heads/master
Commit: 192595391a636bbd5a2a1eac7719462df3b06fee
Parents: fc7bf23
Author: Colin Patrick Mccabe <cm...@cloudera.com>
Authored: Fri Dec 5 14:11:47 2014 -0800
Committer: Colin Patrick Mccabe <cm...@cloudera.com>
Committed: Fri Dec 5 16:50:28 2014 -0800
----------------------------------------------------------------------
htrace-core/pom.xml | 35 ++
htrace-core/src/go/BUILDING.txt | 30 ++
htrace-core/src/go/format.sh | 40 ++
htrace-core/src/go/gobuild.sh | 80 ++++
.../src/go/src/org/apache/htrace/common/rest.go | 26 ++
.../src/go/src/org/apache/htrace/common/span.go | 85 ++++
.../src/org/apache/htrace/common/span_test.go | 67 +++
.../src/org/apache/htrace/common/test_util.go | 74 ++++
.../go/src/org/apache/htrace/common/version.go | 22 +
.../src/go/src/org/apache/htrace/conf/config.go | 201 +++++++++
.../src/org/apache/htrace/conf/config_keys.go | 62 +++
.../src/org/apache/htrace/conf/config_test.go | 121 +++++
.../src/go/src/org/apache/htrace/conf/xml.go | 61 +++
.../src/go/src/org/apache/htrace/htrace/cmd.go | 157 +++++++
.../src/org/apache/htrace/htraced/datastore.go | 438 +++++++++++++++++++
.../org/apache/htrace/htraced/datastore_test.go | 144 ++++++
.../go/src/org/apache/htrace/htraced/htraced.go | 34 ++
.../org/apache/htrace/htraced/mini_htraced.go | 118 +++++
.../go/src/org/apache/htrace/htraced/rest.go | 142 ++++++
.../src/go/src/org/apache/htrace/test/random.go | 71 +++
20 files changed, 2008 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/pom.xml
----------------------------------------------------------------------
diff --git a/htrace-core/pom.xml b/htrace-core/pom.xml
index a8ea8f1..47677d8 100644
--- a/htrace-core/pom.xml
+++ b/htrace-core/pom.xml
@@ -66,6 +66,41 @@ language governing permissions and limitations under the License. -->
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>compile</phase>
+ <id>go_compile</id>
+ <goals><goal>run</goal></goals>
+ <configuration>
+ <tasks>
+ <exec executable="./gobuild.sh"
+ dir="${basedir}/src/go/"
+ failonerror="true">
+ </exec>
+ </tasks>
+ <goals>run</goals>
+ </configuration>
+ </execution>
+ <execution>
+ <phase>test</phase>
+ <id>go_test</id>
+ <goals><goal>run</goal></goals>
+ <configuration>
+ <tasks>
+ <exec executable="./gobuild.sh"
+ dir="${basedir}/src/go/"
+ failonerror="true">
+ <arg value="test"/>
+ </exec>
+ </tasks>
+ <goals>run</goals>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
<plugin>
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/BUILDING.txt
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/BUILDING.txt b/htrace-core/src/go/BUILDING.txt
new file mode 100644
index 0000000..d54d410
--- /dev/null
+++ b/htrace-core/src/go/BUILDING.txt
@@ -0,0 +1,30 @@
+Building the HTrace Go code
+===========================
+The htrace go code consists of 4 main parts:
+* The "htraced" standalone server
+ This is a server which accepts trace spans, and services REST queries.
+
+* The "htrace" command-line program which can query the server
+ This is a simple command-line program which can query the htrace server.
+
+* The htraced Javascript Web UI (not yet implemented)
+
+* The htrace go client library (not yet implemented)
+ This is the equivalent of the Java HTrace client library, but written in Go.
+
+You can build all these parts simply by running "gobuild.sh".
+The binaries will be created in bin/.
+
+Dependencies
+============
+You will need to install:
+* The Go programming language
+* The development package for leveldb (some Linux distros call this "leveldb-devel") containing libleveldb.so
+
+htraced requires libleveldb.so to be in your shared library path in order to run.
+You can set LD_LIBRARY_PATH to the path for this library, or simply install
+libleveldb.so to your system library path.
+
+Testing
+=======
+You can run the unit tests by running "./gobuild.sh test"
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/format.sh
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/format.sh b/htrace-core/src/go/format.sh
new file mode 100755
index 0000000..6b1c8ae
--- /dev/null
+++ b/htrace-core/src/go/format.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+#
+# Reformats the HTrace code.
+#
+# ./format.sh Reformats all code.
+#
+
+die() {
+ echo $@
+ exit 1
+}
+
+# Check for gofmt. It should be installed whenever the go developement tools
+# are installed.
+which gofmt &> /dev/null
+[ $? -ne 0 ] && die "You must install the gofmt code reformatting formatting tool."
+
+# Find go sources. We assume no newlines or whitespace in file names.
+SCRIPT_DIR="$(cd "$( dirname $0 )" && pwd)"
+find "${SCRIPT_DIR}" -noleaf -xdev -name '*.go' | xargs -l gofmt -w
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/gobuild.sh
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/gobuild.sh b/htrace-core/src/go/gobuild.sh
new file mode 100755
index 0000000..135bdd2
--- /dev/null
+++ b/htrace-core/src/go/gobuild.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+#
+# Builds the HTrace server code.
+#
+# ./build.sh Builds the code.
+# ./build.sh test Builds and runs all unit tests.
+# ./build.sh bench Builds and runs all benchmarks
+#
+
+die() {
+ echo $@
+ exit 1
+}
+
+ACTION=get
+if [ $# -gt 0 ]; then
+ if [ "x${1}" == "xbench" ]; then
+ # run benchmarks
+ ACTION="test"
+ set -- "$@" -test.bench=.
+ else
+ # run specified action
+ ACTION="${1}"
+ fi
+ shift
+fi
+
+SCRIPT_DIR="$(cd "$( dirname $0 )" && pwd)"
+export GOPATH="$GOPATH:${SCRIPT_DIR}"
+export GOBIN="${SCRIPT_DIR}/bin"
+mkdir -p ${GOBIN} || die "failed to create ${GOBIN}"
+
+# Check for go
+which go &> /dev/null
+if [ $? -ne 0 ]; then
+ cat <<EOF
+You must install the Golang programming language.
+
+If you are using Debian, try "apt-get install golang".
+For Red Hat, try "yum install go".
+For other distributions and operating systems use your packaging tool.
+EOF
+exit 1
+fi
+
+# Check for libleveldb.so
+if [ -x "/sbin/ldconfig" ]; then
+ # Suse requires ldconfig to be run via the absolute path
+ ldconfig=/sbin/ldconfig
+else
+ ldconfig=ldconfig
+fi
+if "${ldconfig}" -p | grep -q libleveldb; then
+ :
+else
+ echo "You must install the leveldb-devel package (or distro-specific equivalent.)"
+ exit 1
+fi
+
+go "${ACTION}" -v org/apache/htrace/htraced org/apache/htrace/htrace "$@"
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/common/rest.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/rest.go b/htrace-core/src/go/src/org/apache/htrace/common/rest.go
new file mode 100644
index 0000000..1b1db03
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/common/rest.go
@@ -0,0 +1,26 @@
+/*
+ * 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 common
+
+// Info returned by /serverInfo
+type ServerInfo struct {
+ // The server release version.
+ Version string
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/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
new file mode 100644
index 0000000..6f67ce6
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/common/span.go
@@ -0,0 +1,85 @@
+/*
+ * 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 common
+
+import (
+ "encoding/json"
+)
+
+//
+// Represents a trace span.
+//
+// Compatibility notes:
+// We use signed numbers here, even in cases where unsigned would make more sense. This is because
+// Java doesn't support unsigned integers, and we'd like to match the representation used by the
+// Java client. For example, if we log a message about a span id in the Java client, it would be
+// nice if we could match it up with a log message about the same span id in this server, without
+// doing a mental conversion from signed to unsigned.
+//
+// When converting to JSON, we store the 64-bit numbers as strings rather than as integers. This is
+// because JavaScript lacks the ability to handle 64-bit integers. Numbers above about 55 bits will
+// be rounded by Javascript. Since the Javascript UI is a primary consumer of this JSON data, we
+// have to simply pass it as a string.
+//
+
+const INVALID_SPAN_ID = 0
+
+type TraceInfoMap map[string][]byte
+
+type TimelineAnnotation struct {
+ Time int64 `json:"time,string"`
+ Msg string `json:"msg"`
+}
+
+type SpanIdSlice []int64
+
+type SpanData struct {
+ Start int64 `json:"start,string"`
+ Stop int64 `json:"stop,string"`
+ Description string `json:"desc"`
+ TraceId int64 `json:"tid,string"`
+ ParentId int64 `json:"prid,string"`
+ Info TraceInfoMap `json:"info,omitempty"`
+ ProcessId int32 `json:"pid"`
+ TimelineAnnotations []TimelineAnnotation `json:"ta,omitempty"`
+}
+
+type Span struct {
+ SpanId int64 `json:"sid,string"`
+ SpanData
+}
+
+func (span *Span) ToJson() []byte {
+ jbytes, err := json.Marshal(*span)
+ if err != nil {
+ panic(err)
+ }
+ return jbytes
+}
+
+type SpanSlice []Span
+
+func (spans SpanSlice) ToJson() []byte {
+ jbytes, err := json.Marshal(spans)
+ if err != nil {
+ panic(err)
+ }
+ return jbytes
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/common/span_test.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/span_test.go b/htrace-core/src/go/src/org/apache/htrace/common/span_test.go
new file mode 100644
index 0000000..545c669
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/common/span_test.go
@@ -0,0 +1,67 @@
+/*
+ * 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 common
+
+import (
+ "math/rand"
+ "testing"
+)
+
+func TestSpanToJson(t *testing.T) {
+ t.Parallel()
+ span := Span{SpanId: 2305843009213693952,
+ SpanData: SpanData{
+ Start: 123,
+ Stop: 456,
+ Description: "getFileDescriptors",
+ TraceId: 999,
+ ParentId: INVALID_SPAN_ID,
+ ProcessId: 331,
+ }}
+ ExpectStrEqual(t,
+ `{"sid":"2305843009213693952","start":"123","stop":"456","desc":"getFileDescriptors","tid":"999","prid":"0","pid":331}`,
+ string(span.ToJson()))
+}
+
+func TestAnnotatedSpanToJson(t *testing.T) {
+ t.Parallel()
+ span := Span{SpanId: 1305813009213693952,
+ SpanData: SpanData{
+ Start: 1234,
+ Stop: 4567,
+ Description: "getFileDescriptors2",
+ TraceId: 999,
+ ParentId: INVALID_SPAN_ID,
+ ProcessId: 331,
+ TimelineAnnotations: []TimelineAnnotation{
+ TimelineAnnotation{
+ Time: 7777,
+ Msg: "contactedServer",
+ },
+ TimelineAnnotation{
+ Time: 8888,
+ Msg: "passedFd",
+ },
+ },
+ }}
+ ExpectStrEqual(t,
+ `{"sid":"1305813009213693952","start":"1234","stop":"4567","desc":"getFileDescriptors2","tid":"999","prid":"0","pid":331,"ta":[{"time":"7777","msg":"contactedServer"},{"time":"8888","msg":"passedFd"}]}`,
+ string(span.ToJson()))
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/common/test_util.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/test_util.go b/htrace-core/src/go/src/org/apache/htrace/common/test_util.go
new file mode 100644
index 0000000..871c847
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/common/test_util.go
@@ -0,0 +1,74 @@
+/*
+ * 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 common
+
+import (
+ "fmt"
+ "testing"
+ "time"
+)
+
+type Int64Slice []int64
+
+func (p Int64Slice) Len() int { return len(p) }
+func (p Int64Slice) Less(i, j int) bool { return p[i] < p[j] }
+func (p Int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+type SupplierFun func() bool
+
+//
+// Wait for a configurable amount of time for a precondition to become true.
+//
+// Example:
+// WaitFor(time.Minute * 1, time.Millisecond * 1, func() bool {
+// return ht.Store.GetStatistics().NumSpansWritten >= 3
+// })
+//
+func WaitFor(dur time.Duration, poll time.Duration, fun SupplierFun) {
+ if poll == 0 {
+ poll = dur / 10
+ }
+ if poll <= 0 {
+ panic("Can't have a polling time less than zero.")
+ }
+ endTime := time.Now().Add(dur)
+ for {
+ if fun() {
+ return
+ }
+ if !time.Now().Before(endTime) {
+ break
+ }
+ time.Sleep(poll)
+ }
+ panic(fmt.Sprintf("Timed out after %s", dur))
+}
+
+// Trigger a test failure if two strings are not equal.
+func ExpectStrEqual(t *testing.T, expect string, actual string) {
+ if expect != actual {
+ t.Fatalf("Expected:\n%s\nGot:\n%s\n", expect, actual)
+ }
+}
+
+// Trigger a test failure if the JSON representation of two spans are not equals.
+func ExpectSpansEqual(t *testing.T, spanA *Span, spanB *Span) {
+ ExpectStrEqual(t, string(spanA.ToJson()), string(spanB.ToJson()))
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/common/version.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/version.go b/htrace-core/src/go/src/org/apache/htrace/common/version.go
new file mode 100644
index 0000000..789f12f
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/common/version.go
@@ -0,0 +1,22 @@
+/*
+ * 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 common
+
+const RELEASE_VERSION = "1.0-SNAPSHOT"
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/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
new file mode 100644
index 0000000..528d6c1
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/conf/config.go
@@ -0,0 +1,201 @@
+/*
+ * 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 conf
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+)
+
+//
+// The configuration code for HTraced.
+//
+// HTraced can be configured via Hadoop-style XML configuration files, or by passing -Dkey=value
+// command line arguments. Command-line arguments without an equals sign, such as "-Dkey", will be
+// treated as setting the key to "true".
+//
+// Configuration key constants should be defined in config_keys.go. Each key should have a default,
+// which will be used if the user supplies no value, or supplies an invalid value.
+// For that reason, it is not necessary for the Get, GetInt, etc. functions to take a default value
+// argument.
+//
+
+type Config struct {
+ settings map[string]string
+ defaults map[string]string
+}
+
+type Builder struct {
+ // If non-nil, the XML configuration file to read.
+ Reader io.Reader
+
+ // If non-nil, the configuration values to use.
+ Values map[string]string
+
+ // If non-nil, the default configuration values to use.
+ Defaults map[string]string
+
+ // If non-nil, the command-line arguments to use.
+ Argv []string
+}
+
+// Load a configuration from the application's argv, configuration file, and the standard
+// defaults.
+func LoadApplicationConfig() *Config {
+ reader, err := openFile(CONFIG_FILE_NAME, []string{"."})
+ if err != nil {
+ log.Fatal("Error opening config file: " + err.Error())
+ }
+ bld := Builder{}
+ if reader != nil {
+ defer reader.Close()
+ bld.Reader = bufio.NewReader(reader)
+ }
+ bld.Argv = os.Args[1:]
+ bld.Defaults = DEFAULTS
+ var cnf *Config
+ cnf, err = bld.Build()
+ if err != nil {
+ log.Fatal("Error building configuration: " + err.Error())
+ }
+ os.Args = append(os.Args[0:1], bld.Argv...)
+ return cnf
+}
+
+// Attempt to open a configuration file somewhere on the provided list of paths.
+func openFile(cnfName string, paths []string) (io.ReadCloser, error) {
+ for p := range paths {
+ path := fmt.Sprintf("%s%c%s", paths[p], os.PathSeparator, cnfName)
+ file, err := os.Open(path)
+ if err == nil {
+ log.Println("Reading configuration from " + path)
+ return file, nil
+ }
+ if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
+ continue
+ }
+ log.Println("Error opening " + path + " for read: " + err.Error())
+ }
+ return nil, nil
+}
+
+// Build a new configuration object from the provided conf.Builder.
+func (bld *Builder) Build() (*Config, error) {
+ // Load values and defaults
+ cnf := Config{}
+ cnf.settings = make(map[string]string)
+ if bld.Values != nil {
+ for k, v := range bld.Values {
+ cnf.settings[k] = v
+ }
+ }
+ cnf.defaults = make(map[string]string)
+ if bld.Defaults != nil {
+ for k, v := range bld.Defaults {
+ cnf.defaults[k] = v
+ }
+ }
+
+ // Process the configuration file, if we have one
+ if bld.Reader != nil {
+ parseXml(bld.Reader, cnf.settings)
+ }
+
+ // Process command line arguments
+ var i int
+ for i < len(bld.Argv) {
+ str := bld.Argv[i]
+ if strings.HasPrefix(str, "-D") {
+ idx := strings.Index(str, "=")
+ if idx == -1 {
+ key := str[2:]
+ cnf.settings[key] = "true"
+ } else {
+ key := str[2:idx]
+ val := str[idx+1:]
+ cnf.settings[key] = val
+ }
+ bld.Argv = append(bld.Argv[:i], bld.Argv[i+1:]...)
+ } else {
+ i++
+ }
+ }
+ return &cnf, nil
+}
+
+// Get a string configuration key.
+func (cnf *Config) Get(key string) string {
+ ret := cnf.settings[key]
+ if ret != "" {
+ return ret
+ }
+ return cnf.defaults[key]
+}
+
+// Get a boolean configuration key.
+func (cnf *Config) GetBool(key string) bool {
+ str := cnf.settings[key]
+ ret, err := strconv.ParseBool(str)
+ if err == nil {
+ return ret
+ }
+ str = cnf.defaults[key]
+ ret, err = strconv.ParseBool(str)
+ if err == nil {
+ return ret
+ }
+ return false
+}
+
+// Get an integer configuration key.
+func (cnf *Config) GetInt(key string) int {
+ str := cnf.settings[key]
+ ret, err := strconv.Atoi(str)
+ if err == nil {
+ return ret
+ }
+ str = cnf.defaults[key]
+ ret, err = strconv.Atoi(str)
+ if err == nil {
+ return ret
+ }
+ return 0
+}
+
+// Get an int64 configuration key.
+func (cnf *Config) GetInt64(key string) int64 {
+ str := cnf.settings[key]
+ ret, err := strconv.ParseInt(str, 10, 64)
+ if err == nil {
+ return ret
+ }
+ str = cnf.defaults[key]
+ ret, err = strconv.ParseInt(str, 10, 64)
+ if err == nil {
+ return ret
+ }
+ return 0
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/conf/config_keys.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/conf/config_keys.go b/htrace-core/src/go/src/org/apache/htrace/conf/config_keys.go
new file mode 100644
index 0000000..b4e5994
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/conf/config_keys.go
@@ -0,0 +1,62 @@
+/*
+ * 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 conf
+
+import (
+ "fmt"
+ "os"
+)
+
+//
+// Configuration keys for HTrace.
+//
+
+// The platform-specific path separator. Usually slash.
+var PATH_SEP string = fmt.Sprintf("%c", os.PathSeparator)
+
+// The platform-specific path list separator. Usually colon.
+var PATH_LIST_SEP string = fmt.Sprintf("%c", os.PathListSeparator)
+
+// The name of the XML configuration file to look for.
+const CONFIG_FILE_NAME = "htraced.xml"
+
+// The web address to start the REST server on.
+const HTRACE_WEB_ADDRESS = "web.address"
+
+// The default port for the Htrace web address.
+const HTRACE_WEB_ADDRESS_DEFAULT_PORT = 9095
+
+// The directories to put the data store into. Separated by PATH_LIST_SEP.
+const HTRACE_DATA_STORE_DIRECTORIES = "data.store.directories"
+
+// Boolean key which indicates whether we should clear data on startup.
+const HTRACE_DATA_STORE_CLEAR = "data.store.clear"
+
+// How many writes to buffer before applying backpressure to span senders.
+const HTRACE_DATA_STORE_SPAN_BUFFER_SIZE = "data.store.span.buffer.size"
+
+// Default values for HTrace configuration keys.
+var DEFAULTS = map[string]string{
+ HTRACE_WEB_ADDRESS: fmt.Sprintf("0.0.0.0:%d", HTRACE_WEB_ADDRESS_DEFAULT_PORT),
+ HTRACE_DATA_STORE_DIRECTORIES: PATH_SEP + "tmp" + PATH_SEP + "htrace1" +
+ PATH_LIST_SEP + PATH_SEP + "tmp" + PATH_SEP + "htrace2",
+ HTRACE_DATA_STORE_CLEAR: "false",
+ HTRACE_DATA_STORE_SPAN_BUFFER_SIZE: "100",
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/conf/config_test.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/conf/config_test.go b/htrace-core/src/go/src/org/apache/htrace/conf/config_test.go
new file mode 100644
index 0000000..85b1517
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/conf/config_test.go
@@ -0,0 +1,121 @@
+/*
+ * 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 conf
+
+import (
+ "strings"
+ "testing"
+)
+
+// Test that parsing command-line arguments of the form -Dfoo=bar works.
+func TestParseArgV(t *testing.T) {
+ t.Parallel()
+ argv := []string{"-Dfoo=bar", "-Dbaz=123", "-DsillyMode"}
+ bld := &ConfigBuilder{Argv: argv}
+ cnf, err := bld.Build()
+ if err != nil {
+ t.Fatal()
+ }
+ if "bar" != cnf.Get("foo") {
+ t.Fatal()
+ }
+ if 123 != cnf.GetInt("baz") {
+ t.Fatal()
+ }
+ if !cnf.GetBool("sillyMode") {
+ t.Fatal()
+ }
+ if cnf.GetBool("otherSillyMode") {
+ t.Fatal()
+ }
+}
+
+// Test that default values work.
+// Defaults are used only when the configuration option is not present or can't be parsed.
+func TestDefaults(t *testing.T) {
+ t.Parallel()
+ argv := []string{"-Dfoo=bar", "-Dbaz=invalidNumber"}
+ defaults := map[string]string{
+ "foo": "notbar",
+ "baz": "456",
+ "foo2": "4611686018427387904",
+ }
+ bld := &ConfigBuilder{Argv: argv, Defaults: defaults}
+ cnf, err := bld.Build()
+ if err != nil {
+ t.Fatal()
+ }
+ if "bar" != cnf.Get("foo") {
+ t.Fatal()
+ }
+ if 456 != cnf.GetInt("baz") {
+ t.Fatal()
+ }
+ if 4611686018427387904 != cnf.GetInt64("foo2") {
+ t.Fatal()
+ }
+}
+
+// Test that we can parse our XML configuration file.
+func TestXmlConfigurationFile(t *testing.T) {
+ t.Parallel()
+ xml := `
+<?xml version="1.0"?>
+<?xml-stylesheet type=\"text/xsl\" href=\"configuration.xsl\"?>
+<configuration>
+ <property>
+ <name>foo.bar</name>
+ <value>123</value>
+ </property>
+ <property>
+ <name>foo.baz</name>
+ <value>xmlValue</value>
+ </property>
+ <!--<property>
+ <name>commented.out</name>
+ <value>stuff</value>
+ </property>-->
+</configuration>
+`
+ xmlReader := strings.NewReader(xml)
+ argv := []string{"-Dfoo.bar=456"}
+ defaults := map[string]string{
+ "foo.bar": "789",
+ "cmdline.opt": "4611686018427387904",
+ }
+ bld := &ConfigBuilder{Argv: argv, Defaults: defaults, Reader: xmlReader}
+ cnf, err := bld.Build()
+ if err != nil {
+ t.Fatal()
+ }
+ // The command-line argument takes precedence over the XML and the defaults.
+ if 456 != cnf.GetInt("foo.bar") {
+ t.Fatal()
+ }
+ if "xmlValue" != cnf.Get("foo.baz") {
+ t.Fatalf("foo.baz = %s", cnf.Get("foo.baz"))
+ }
+ if "" != cnf.Get("commented.out") {
+ t.Fatal()
+ }
+ if 4611686018427387904 != cnf.GetInt64("cmdline.opt") {
+ t.Fatal()
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/conf/xml.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/conf/xml.go b/htrace-core/src/go/src/org/apache/htrace/conf/xml.go
new file mode 100644
index 0000000..de14bc5
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/conf/xml.go
@@ -0,0 +1,61 @@
+/*
+ * 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 conf
+
+import (
+ "encoding/xml"
+ "io"
+ "log"
+)
+
+type configuration struct {
+ Properties []propertyXml `xml:"property"`
+}
+
+type propertyXml struct {
+ Name string `xml:"name"`
+ Value string `xml:"value"`
+}
+
+// Parse an XML configuration file.
+func parseXml(reader io.Reader, m map[string]string) error {
+ dec := xml.NewDecoder(reader)
+ configurationXml := configuration{}
+ err := dec.Decode(&configurationXml)
+ if err != nil {
+ return err
+ }
+ props := configurationXml.Properties
+ for p := range props {
+ key := props[p].Name
+ value := props[p].Value
+ if key == "" {
+ log.Println("Warning: ignoring element with missing or empty <name>.")
+ continue
+ }
+ if value == "" {
+ log.Println("Warning: ignoring element with key " + key + " with missing or empty <value>.")
+ continue
+ }
+ //log.Printf("setting %s to %s\n", key, value)
+ m[key] = value
+ }
+ return nil
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/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
new file mode 100644
index 0000000..d4bb253
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
@@ -0,0 +1,157 @@
+/*
+ * 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 (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "gopkg.in/alecthomas/kingpin.v1"
+ "io/ioutil"
+ "net/http"
+ "org/apache/htrace/common"
+ "org/apache/htrace/conf"
+ "os"
+)
+
+func main() {
+ // Load htraced configuration
+ cnf := conf.LoadApplicationConfig()
+
+ // Parse argv
+ app := kingpin.New("htrace", "The HTrace tracing utility.")
+ addr := app.Flag("addr", "Server address.").
+ Default(cnf.Get(conf.HTRACE_WEB_ADDRESS)).TCP()
+ 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.")
+ findSpanId := findSpan.Flag("id", "Span ID to find, as a signed decimal 64-bit "+
+ "number").Required().Int64()
+ findChildren := app.Command("findChildren", "Print out the span IDs that are children of a given span ID.")
+ parentSpanId := findChildren.Flag("id", "Span ID to print children for, as a signed decimal 64-bit "+
+ "number").Required().Int64()
+ childLim := findChildren.Flag("lim", "Maximum number of child IDs to print.").Default("20").Int()
+
+ // Handle operation
+ switch kingpin.MustParse(app.Parse(os.Args[1:])) {
+ case version.FullCommand():
+ os.Exit(printVersion())
+ case serverInfo.FullCommand():
+ os.Exit(printServerInfo(addr.String()))
+ case findSpan.FullCommand():
+ os.Exit(doFindSpan(addr.String(), *findSpanId))
+ case findChildren.FullCommand():
+ os.Exit(doFindChildren(addr.String(), *parentSpanId, *childLim))
+ }
+
+ app.UsageErrorf(os.Stderr, "You must supply a command to run.")
+}
+
+// Print the version of the htrace binary.
+func printVersion() int {
+ fmt.Printf("Running htrace command version %s.\n", common.RELEASE_VERSION)
+ return 0
+}
+
+// Print information retrieved from an htraced server via /serverInfo
+func printServerInfo(restAddr string) int {
+ buf, err := makeRestRequest(restAddr, "serverInfo")
+ 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.Printf("HTraced server version %s\n", info.Version)
+ return 0
+}
+
+// Print information about a trace span.
+func doFindSpan(restAddr string, sid int64) int {
+ buf, err := makeRestRequest(restAddr, fmt.Sprintf("findSid?sid=%d", sid))
+ if err != nil {
+ fmt.Printf("%s\n", err.Error())
+ return 1
+ }
+ 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
+ }
+ pbuf, err := json.MarshalIndent(span, "", " ")
+ if err != nil {
+ fmt.Println("Error: error pretty-printing span to JSON: %s", err.Error())
+ return 1
+ }
+ fmt.Printf("%s\n", string(pbuf))
+ return 0
+}
+
+// Find information about the children of a span.
+func doFindChildren(restAddr string, sid int64, lim int) int {
+ buf, err := makeRestRequest(restAddr, fmt.Sprintf("findChildren?sid=%d&lim=%d", sid, lim))
+ if err != nil {
+ fmt.Printf("%s\n", err.Error())
+ return 1
+ }
+ var spanIds []int64
+ err = json.Unmarshal(buf, &spanIds)
+ if err != nil {
+ fmt.Printf("Error: error unmarshalling response body %s: %s\n",
+ string(buf), err.Error())
+ return 1
+ }
+ pbuf, err := json.MarshalIndent(spanIds, "", " ")
+ if err != nil {
+ fmt.Println("Error: error pretty-printing span IDs to JSON: %s", err.Error())
+ return 1
+ }
+ fmt.Printf("%s\n", string(pbuf))
+ return 0
+}
+
+// Print information retrieved from an htraced server via /serverInfo
+func makeRestRequest(restAddr string, reqName string) ([]byte, error) {
+ url := fmt.Sprintf("http://%s/%s", restAddr, reqName)
+ req, err := http.NewRequest("GET", url, nil)
+ 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/19259539/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
new file mode 100644
index 0000000..d78e369
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go
@@ -0,0 +1,438 @@
+/*
+ * 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 (
+ "bytes"
+ "encoding/gob"
+ "github.com/jmhodges/levigo"
+ "log"
+ "org/apache/htrace/common"
+ "org/apache/htrace/conf"
+ "os"
+ "strings"
+ "sync/atomic"
+ "syscall"
+)
+
+//
+// The data store code for HTraced.
+//
+// This code stores the trace spans. We use levelDB here so that we don't have to store everything
+// in memory at all times. The data is sharded across multiple levelDB databases in multiple
+// directories. Normally, these multiple directories will be on multiple disk drives.
+//
+// The main emphasis in the HTraceD data store is on quickly and efficiently storing trace span data
+// coming from many daemons. Durability is not as big a concern as in some data stores, since
+// losing a little bit of trace data if htraced goes down is not critical. We use the "gob" package
+// for serialization. We assume that there will be many more writes than reads.
+//
+// TODO: implement redundancy (storing data on more than 1 drive)
+// TODO: implement re-loading old span data
+//
+// Schema
+// m -> dataStoreMetadata
+// s[8-byte-big-endian-sid] -> SpanData
+// p[8-byte-big-endian-parent-sid][8-byte-big-endian-child-sid] -> {}
+// t[8-byte-big-endian-time][8-byte-big-endian-child-sid] -> {}
+//
+
+const DATA_STORE_VERSION = 1
+
+var EMPTY_BYTE_BUF []byte = []byte{}
+
+type Statistics struct {
+ NumSpansWritten uint64
+}
+
+func (stats *Statistics) IncrementWrittenSpans() {
+ atomic.AddUint64(&stats.NumSpansWritten, 1)
+}
+
+// Make a copy of the statistics structure, using atomic operations.
+func (stats *Statistics) Copy() *Statistics {
+ return &Statistics{
+ NumSpansWritten: atomic.LoadUint64(&stats.NumSpansWritten),
+ }
+}
+
+// Translate a span id into a leveldb key.
+func makeKey(tag byte, sid int64) []byte {
+ id := uint64(sid)
+ return []byte{
+ tag,
+ byte(0xff & (id >> 56)),
+ byte(0xff & (id >> 48)),
+ byte(0xff & (id >> 40)),
+ byte(0xff & (id >> 32)),
+ byte(0xff & (id >> 24)),
+ byte(0xff & (id >> 16)),
+ byte(0xff & (id >> 8)),
+ byte(0xff & (id >> 0)),
+ }
+}
+
+func keyToInt(key []byte) int64 {
+ var id uint64
+ id = (uint64(key[0]) << 56) |
+ (uint64(key[1]) << 48) |
+ (uint64(key[2]) << 40) |
+ (uint64(key[3]) << 32) |
+ (uint64(key[4]) << 24) |
+ (uint64(key[5]) << 16) |
+ (uint64(key[6]) << 8) |
+ (uint64(key[7]) << 0)
+ return int64(id)
+}
+
+func makeSecondaryKey(tag byte, first int64, second int64) []byte {
+ fir := uint64(first)
+ sec := uint64(second)
+ return []byte{
+ tag,
+ byte(0xff & (fir >> 56)),
+ byte(0xff & (fir >> 48)),
+ byte(0xff & (fir >> 40)),
+ byte(0xff & (fir >> 32)),
+ byte(0xff & (fir >> 24)),
+ byte(0xff & (fir >> 16)),
+ byte(0xff & (fir >> 8)),
+ byte(0xff & (fir >> 0)),
+ byte(0xff & (sec >> 56)),
+ byte(0xff & (sec >> 48)),
+ byte(0xff & (sec >> 40)),
+ byte(0xff & (sec >> 32)),
+ byte(0xff & (sec >> 24)),
+ byte(0xff & (sec >> 16)),
+ byte(0xff & (sec >> 8)),
+ byte(0xff & (sec >> 0)),
+ }
+}
+
+// A single directory containing a levelDB instance.
+type shard struct {
+ // The data store that this shard is part of
+ store *dataStore
+
+ // The LevelDB instance.
+ ldb *levigo.DB
+
+ // The path to the leveldb directory this shard is managing.
+ path string
+
+ // Incoming requests to write Spans.
+ incoming chan *common.Span
+
+ // The channel we will send a bool to when we exit.
+ exited chan bool
+}
+
+// Metadata about the DataStore.
+type dataStoreMetadata struct {
+ // The DataStore version.
+ Version int32
+}
+
+// Write the metadata key to a shard.
+func (shd *shard) WriteMetadata(meta *dataStoreMetadata) error {
+ w := new(bytes.Buffer)
+ encoder := gob.NewEncoder(w)
+ err := encoder.Encode(meta)
+ if err != nil {
+ return err
+ }
+ return shd.ldb.Put(shd.store.writeOpts, []byte("m"), w.Bytes())
+}
+
+// Process incoming spans for a shard.
+func (shd *shard) processIncoming() {
+ for {
+ span := <-shd.incoming
+ if span == nil {
+ log.Printf("Shard processor for %s exiting.", shd.path)
+ shd.exited <- true
+ return
+ }
+ err := shd.writeSpan(span)
+ if err != nil {
+ log.Fatal("Shard processor for %s got fatal error %s.", shd.path, err.Error())
+ }
+ //log.Printf("Shard processor for %s wrote span %s.", shd.path, span.ToJson())
+ }
+}
+
+func (shd *shard) writeSpan(span *common.Span) error {
+ batch := levigo.NewWriteBatch()
+ defer batch.Close()
+
+ // Add SpanData to batch.
+ spanDataBuf := new(bytes.Buffer)
+ spanDataEnc := gob.NewEncoder(spanDataBuf)
+ err := spanDataEnc.Encode(span.SpanData)
+ if err != nil {
+ return err
+ }
+ batch.Put(makeKey('s', span.SpanId), spanDataBuf.Bytes())
+
+ // Add this to the parent index.
+ batch.Put(makeSecondaryKey('p', span.ParentId, span.SpanId), EMPTY_BYTE_BUF)
+
+ // Add this to the timeline index.
+ batch.Put(makeSecondaryKey('t', span.ParentId, span.SpanId), EMPTY_BYTE_BUF)
+
+ err = shd.ldb.Write(shd.store.writeOpts, batch)
+ if err != nil {
+ return err
+ }
+ shd.store.stats.IncrementWrittenSpans()
+ if shd.store.WrittenSpans != nil {
+ shd.store.WrittenSpans <- span
+ }
+ return nil
+}
+
+func (shd *shard) FindChildren(sid int64, childIds []int64, lim int32) ([]int64, int32, error) {
+ searchKey := makeKey('p', sid)
+ iter := shd.ldb.NewIterator(shd.store.readOpts)
+ defer iter.Close()
+ iter.Seek(searchKey)
+ for {
+ if !iter.Valid() {
+ break
+ }
+ if lim == 0 {
+ break
+ }
+ key := iter.Key()
+ if !bytes.HasPrefix(key, searchKey) {
+ break
+ }
+ id := keyToInt(key[9:])
+ childIds = append(childIds, id)
+ lim--
+ iter.Next()
+ }
+ return childIds, lim, nil
+}
+
+// Close a shard.
+func (shd *shard) Close() {
+ shd.incoming <- nil
+ log.Printf("Waiting for %s to exit...", shd.path)
+ if shd.exited != nil {
+ <-shd.exited
+ }
+ shd.ldb.Close()
+ log.Printf("Closed %s...", shd.path)
+}
+
+// The Data Store.
+type dataStore struct {
+ // The shards which manage our LevelDB instances.
+ shards []*shard
+
+ // I/O statistics for all shards.
+ stats Statistics
+
+ // The read options to use for LevelDB.
+ readOpts *levigo.ReadOptions
+
+ // The write options to use for LevelDB.
+ writeOpts *levigo.WriteOptions
+
+ // If non-null, a channel we will send spans to once we finish writing them. This is only used
+ // for testing.
+ WrittenSpans chan *common.Span
+}
+
+func CreateDataStore(cnf *conf.Config, writtenSpans chan *common.Span) (*dataStore, error) {
+ // Get the configuration.
+ clearStored := cnf.GetBool(conf.HTRACE_DATA_STORE_CLEAR)
+ dirsStr := cnf.Get(conf.HTRACE_DATA_STORE_DIRECTORIES)
+ dirs := strings.Split(dirsStr, conf.PATH_LIST_SEP)
+
+ // If we return an error, close the store.
+ var err error
+ store := &dataStore{shards: []*shard{}, WrittenSpans: writtenSpans}
+ defer func() {
+ if err != nil {
+ store.Close()
+ store = nil
+ }
+ }()
+
+ store.readOpts = levigo.NewReadOptions()
+ store.readOpts.SetFillCache(true)
+ store.writeOpts = levigo.NewWriteOptions()
+ store.writeOpts.SetSync(false)
+
+ // Open all shards
+ for idx := range dirs {
+ path := dirs[idx] + conf.PATH_SEP + "db"
+ err := os.MkdirAll(path, 0777)
+ if err != nil {
+ e, ok := err.(*os.PathError)
+ if !ok || e.Err != syscall.EEXIST {
+ return nil, err
+ }
+ if !clearStored {
+ // TODO: implement re-opening saved data
+ log.Println("Error: path " + path + "already exists.")
+ return nil, err
+ } else {
+ err = os.RemoveAll(path)
+ if err != nil {
+ log.Println("Failed to create " + path + ": " + err.Error())
+ return nil, err
+ }
+ log.Println("Cleared " + path)
+ }
+ }
+ var shd *shard
+ shd, err = CreateShard(store, cnf, path)
+ if err != nil {
+ log.Printf("Error creating shard %s: %s", path, err.Error())
+ return nil, err
+ }
+ store.shards = append(store.shards, shd)
+ }
+ meta := &dataStoreMetadata{Version: DATA_STORE_VERSION}
+ for idx := range store.shards {
+ shd := store.shards[idx]
+ err := shd.WriteMetadata(meta)
+ if err != nil {
+ log.Println("Failed to write metadata to " + store.shards[idx].path + ": " + err.Error())
+ return nil, err
+ }
+ shd.exited = make(chan bool, 1)
+ go shd.processIncoming()
+ }
+ return store, nil
+}
+
+func CreateShard(store *dataStore, cnf *conf.Config, path string) (*shard, error) {
+ var shd *shard
+ //filter := levigo.NewBloomFilter(10)
+ //defer filter.Close()
+ openOpts := levigo.NewOptions()
+ defer openOpts.Close()
+ openOpts.SetCreateIfMissing(true)
+ //openOpts.SetFilterPolicy(filter)
+ ldb, err := levigo.Open(path, openOpts)
+ if err != nil {
+ log.Println("LevelDB failed to open " + path + ": " + err.Error())
+ return nil, err
+ }
+ defer func() {
+ if shd == nil {
+ ldb.Close()
+ }
+ }()
+ spanBufferSize := cnf.GetInt(conf.HTRACE_DATA_STORE_SPAN_BUFFER_SIZE)
+ shd = &shard{store: store, ldb: ldb, path: path,
+ incoming: make(chan *common.Span, spanBufferSize)}
+ log.Println("LevelDB opened " + path)
+ return shd, nil
+}
+
+func (store *dataStore) GetStatistics() *Statistics {
+ return store.stats.Copy()
+}
+
+// Close the DataStore.
+func (store *dataStore) Close() {
+ for idx := range store.shards {
+ store.shards[idx].Close()
+ }
+ if store.readOpts != nil {
+ store.readOpts.Close()
+ }
+ if store.writeOpts != nil {
+ store.writeOpts.Close()
+ }
+}
+
+// Get the index of the shard which stores the given spanId.
+func (store *dataStore) getShardIndex(spanId int64) int {
+ return int(uint64(spanId) % uint64(len(store.shards)))
+}
+
+func (store *dataStore) WriteSpan(span *common.Span) {
+ store.shards[store.getShardIndex(span.SpanId)].incoming <- span
+}
+
+func (store *dataStore) FindSpan(sid int64) *common.Span {
+ return store.shards[store.getShardIndex(sid)].FindSpan(sid)
+}
+
+func (shd *shard) FindSpan(sid int64) *common.Span {
+ buf, err := shd.ldb.Get(shd.store.readOpts, makeKey('s', sid))
+ if err != nil {
+ if strings.Index(err.Error(), "NotFound:") != -1 {
+ return nil
+ }
+ log.Printf("Shard(%s): FindSpan(%d) error: %s\n",
+ shd.path, sid, err.Error())
+ return nil
+ }
+ // check for empty buf here?
+ r := bytes.NewBuffer(buf)
+ decoder := gob.NewDecoder(r)
+ data := common.SpanData{}
+ err = decoder.Decode(&data)
+ if err != nil {
+ log.Printf("Shard(%s): FindSpan(%d) decode error: %s\n",
+ shd.path, sid, err.Error())
+ return nil
+ }
+ return &common.Span{SpanId: sid, SpanData: data}
+}
+
+// Find the children of a given span id.
+func (store *dataStore) FindChildren(sid int64, lim int32) []int64 {
+ childIds := make([]int64, 0)
+ var err error
+
+ startIdx := store.getShardIndex(sid)
+ idx := startIdx
+ numShards := len(store.shards)
+ for {
+ if lim == 0 {
+ break
+ }
+ shd := store.shards[idx]
+ childIds, lim, err = shd.FindChildren(sid, childIds, lim)
+ if err != nil {
+ log.Printf("Shard(%s): FindChildren(%d) error: %s\n",
+ shd.path, sid, err.Error())
+ }
+ idx++
+ if idx >= numShards {
+ idx = 0
+ }
+ if idx == startIdx {
+ break
+ }
+ }
+ return childIds
+}
+
+//func (store *dataStore) FindByTimeRange(startTime int64, endTime int64, lim int32) []int64 {
+//}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/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
new file mode 100644
index 0000000..c74b572
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go
@@ -0,0 +1,144 @@
+/*
+ * 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"
+ "org/apache/htrace/common"
+ "org/apache/htrace/test"
+ "sort"
+ "testing"
+)
+
+// Test creating and tearing down a datastore.
+func TestCreateDatastore(t *testing.T) {
+ htraceBld := &MiniHTracedBuilder{Name: "TestCreateDatastore", NumDataDirs: 3}
+ ht, err := htraceBld.Build()
+ if err != nil {
+ t.Fatalf("failed to create datastore: %s", err.Error())
+ }
+ defer ht.Close()
+}
+
+var SIMPLE_TEST_SPANS []common.Span = []common.Span{
+ common.Span{SpanId: 1,
+ SpanData: common.SpanData{
+ Start: 123,
+ Stop: 456,
+ Description: "getFileDescriptors",
+ TraceId: 999,
+ ParentId: common.INVALID_SPAN_ID,
+ ProcessId: 331,
+ }},
+ common.Span{SpanId: 2,
+ SpanData: common.SpanData{
+ Start: 125,
+ Stop: 200,
+ Description: "openFd",
+ TraceId: 999,
+ ParentId: 1,
+ ProcessId: 332,
+ }},
+ common.Span{SpanId: 3,
+ SpanData: common.SpanData{
+ Start: 200,
+ Stop: 456,
+ Description: "passFd",
+ TraceId: 999,
+ ParentId: 1,
+ ProcessId: 332,
+ }},
+}
+
+func createSpans(spans []common.Span, store *dataStore) {
+ for idx := range spans {
+ store.WriteSpan(&spans[idx])
+ }
+ // Wait the spans to be created
+ for i := 0; i < 3; i++ {
+ <-store.WrittenSpans
+ }
+}
+
+// Test creating a datastore and adding some spans.
+func TestDatastoreWriteAndRead(t *testing.T) {
+ t.Parallel()
+ htraceBld := &MiniHTracedBuilder{Name: "TestDatastoreWriteAndRead",
+ WrittenSpans: make(chan *common.Span, 100)}
+ ht, err := htraceBld.Build()
+ if err != nil {
+ panic(err)
+ }
+ defer ht.Close()
+ createSpans(SIMPLE_TEST_SPANS, ht.Store)
+ if ht.Store.GetStatistics().NumSpansWritten < uint64(len(SIMPLE_TEST_SPANS)) {
+ t.Fatal()
+ }
+ span := ht.Store.FindSpan(1)
+ if span == nil {
+ t.Fatal()
+ }
+ if span.SpanId != 1 {
+ t.Fatal()
+ }
+ common.ExpectSpansEqual(t, span, &SIMPLE_TEST_SPANS[0])
+ children := ht.Store.FindChildren(1, 1)
+ if len(children) != 1 {
+ t.Fatalf("expected 1 child, but got %d\n", len(children))
+ }
+ children = ht.Store.FindChildren(1, 2)
+ if len(children) != 2 {
+ t.Fatalf("expected 2 children, but got %d\n", len(children))
+ }
+ sort.Sort(common.Int64Slice(children))
+ if children[0] != 2 {
+ t.Fatal()
+ }
+ if children[1] != 3 {
+ t.Fatal()
+ }
+}
+
+func BenchmarkDatastoreWrites(b *testing.B) {
+ htraceBld := &MiniHTracedBuilder{Name: "BenchmarkDatastoreWrites",
+ WrittenSpans: make(chan *common.Span, b.N)}
+ ht, err := htraceBld.Build()
+ if err != nil {
+ panic(err)
+ }
+ defer ht.Close()
+ rnd := rand.New(rand.NewSource(1))
+ allSpans := make([]*common.Span, b.N)
+ // Write many random spans.
+ for n := 0; n < b.N; n++ {
+ span := test.NewRandomSpan(rnd, allSpans[0:n])
+ ht.Store.WriteSpan(span)
+ allSpans[n] = span
+ }
+ // Wait for all the spans to be written.
+ for n := 0; n < b.N; n++ {
+ <-ht.Store.WrittenSpans
+ }
+ spansWritten := ht.Store.GetStatistics().NumSpansWritten
+ if spansWritten < uint64(b.N) {
+ b.Fatal("incorrect statistics: expected %d spans to be written, but only got %d",
+ b.N, spansWritten)
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/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
new file mode 100644
index 0000000..d6cfe2a
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go
@@ -0,0 +1,34 @@
+/*
+ * 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 (
+ "log"
+ "org/apache/htrace/conf"
+)
+
+func main() {
+ cnf := conf.LoadApplicationConfig()
+ store, err := CreateDataStore(cnf, nil)
+ if err != nil {
+ log.Fatalf("Error creating datastore: %s\n", err.Error())
+ }
+ startRestServer(cnf, store)
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/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
new file mode 100644
index 0000000..43d7e23
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go
@@ -0,0 +1,118 @@
+/*
+ * 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 (
+ "fmt"
+ "io/ioutil"
+ "org/apache/htrace/common"
+ "org/apache/htrace/conf"
+ "os"
+ "strings"
+)
+
+//
+// MiniHTraceD is used in unit tests to set up a daemon with certain settings.
+// It takes care of things like creating and cleaning up temporary directories.
+//
+
+// The default number of managed data directories to use.
+const DEFAULT_NUM_DATA_DIRS = 2
+
+// Builds a MiniHTraced object.
+type MiniHTracedBuilder struct {
+ // The name of the MiniHTraced to build. This shows up in the test directory name and some
+ // other places.
+ Name string
+
+ // The configuration values to use for the MiniHTraced.
+ // If ths is nil, we use the default configuration for everything.
+ Cnf map[string]string
+
+ // The number of managed data directories to create.
+ // If this is 0, it defaults to DEFAULT_NUM_DATA_DIRS.
+ NumDataDirs int
+
+ // If non-null, the WrittenSpans channel to use when creating the DataStore.
+ WrittenSpans chan *common.Span
+}
+
+type MiniHTraced struct {
+ Name string
+ Cnf *conf.Config
+ DataDirs []string
+ Store *dataStore
+}
+
+func (bld *MiniHTracedBuilder) Build() (*MiniHTraced, error) {
+ var err error
+ var store *dataStore
+ if bld.Name == "" {
+ bld.Name = "HTraceTest"
+ }
+ if bld.Cnf == nil {
+ bld.Cnf = make(map[string]string)
+ }
+ if bld.NumDataDirs == 0 {
+ bld.NumDataDirs = DEFAULT_NUM_DATA_DIRS
+ }
+ dataDirs := make([]string, bld.NumDataDirs)
+ defer func() {
+ if err != nil {
+ if store != nil {
+ store.Close()
+ }
+ for idx := range dataDirs {
+ if dataDirs[idx] != "" {
+ os.RemoveAll(dataDirs[idx])
+ }
+ }
+ }
+ }()
+ for idx := range dataDirs {
+ dataDirs[idx], err = ioutil.TempDir(os.TempDir(),
+ fmt.Sprintf("%s%d", bld.Name, idx+1))
+ if err != nil {
+ return nil, err
+ }
+ }
+ bld.Cnf[conf.HTRACE_DATA_STORE_DIRECTORIES] = strings.Join(dataDirs, conf.PATH_LIST_SEP)
+ cnfBld := conf.Builder{Values: bld.Cnf, Defaults: conf.DEFAULTS}
+ cnf, err := cnfBld.Build()
+ if err != nil {
+ return nil, err
+ }
+ store, err = CreateDataStore(cnf, bld.WrittenSpans)
+ if err != nil {
+ return nil, err
+ }
+ return &MiniHTraced{
+ Cnf: cnf,
+ DataDirs: dataDirs,
+ Store: store,
+ }, nil
+}
+
+func (ht *MiniHTraced) 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/19259539/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
new file mode 100644
index 0000000..8374c40
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
@@ -0,0 +1,142 @@
+/*
+ * 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 (
+ "encoding/json"
+ "log"
+ "net/http"
+ "org/apache/htrace/common"
+ "org/apache/htrace/conf"
+ "strconv"
+)
+
+type serverInfoHandler struct {
+}
+
+func (handler *serverInfoHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ version := common.ServerInfo{Version: common.RELEASE_VERSION}
+ buf, err := json.Marshal(&version)
+ if err != nil {
+ log.Printf("error marshalling ServerInfo: %s\n", err.Error())
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ w.Write(buf)
+}
+
+type dataStoreHandler struct {
+ store *dataStore
+}
+
+func (hand *dataStoreHandler) getReqField64(fieldName string, w http.ResponseWriter,
+ req *http.Request) (int64, bool) {
+ str := req.FormValue(fieldName)
+ if str == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("No " + fieldName + " specified."))
+ return -1, false
+ }
+ val, err := strconv.ParseInt(str, 10, 64)
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Error parsing " + fieldName + ": " + err.Error()))
+ return -1, false
+ }
+ return val, true
+}
+
+func (hand *dataStoreHandler) getReqField32(fieldName string, w http.ResponseWriter,
+ req *http.Request) (int32, bool) {
+ str := req.FormValue(fieldName)
+ if str == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("No " + fieldName + " specified."))
+ return -1, false
+ }
+ val, err := strconv.ParseInt(str, 10, 32)
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Error parsing " + fieldName + ": " + err.Error()))
+ return -1, false
+ }
+ return int32(val), true
+}
+
+type findSidHandler struct {
+ dataStoreHandler
+}
+
+func (hand *findSidHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ req.ParseForm()
+ sid, ok := hand.getReqField64("sid", w, req)
+ if !ok {
+ return
+ }
+ span := hand.store.FindSpan(sid)
+ if span == nil {
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+ w.Write(span.ToJson())
+}
+
+type findChildrenHandler struct {
+ dataStoreHandler
+}
+
+func (hand *findChildrenHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ req.ParseForm()
+ sid, ok := hand.getReqField64("sid", w, req)
+ if !ok {
+ return
+ }
+ var lim int32
+ lim, ok = hand.getReqField32("lim", w, req)
+ if !ok {
+ return
+ }
+ children := hand.store.FindChildren(sid, lim)
+ if len(children) == 0 {
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+ jbytes, err := json.Marshal(children)
+ if err != nil {
+ panic(err)
+ }
+ w.Write(jbytes)
+}
+
+func startRestServer(cnf *conf.Config, store *dataStore) {
+ mux := http.NewServeMux()
+
+ serverInfoH := &serverInfoHandler{}
+ mux.Handle("/serverInfo", serverInfoH)
+
+ findSidH := &findSidHandler{dataStoreHandler: dataStoreHandler{store: store}}
+ mux.Handle("/findSid", findSidH)
+
+ findChildrenH := &findChildrenHandler{dataStoreHandler: dataStoreHandler{store: store}}
+ mux.Handle("/findChildren", findChildrenH)
+
+ http.ListenAndServe(cnf.Get(conf.HTRACE_WEB_ADDRESS), mux)
+ log.Println("Started REST server...")
+}
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/19259539/htrace-core/src/go/src/org/apache/htrace/test/random.go
----------------------------------------------------------------------
diff --git a/htrace-core/src/go/src/org/apache/htrace/test/random.go b/htrace-core/src/go/src/org/apache/htrace/test/random.go
new file mode 100644
index 0000000..1272515
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/test/random.go
@@ -0,0 +1,71 @@
+/*
+ * 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 test
+
+import (
+ "math/rand"
+ "org/apache/htrace/common"
+)
+
+func NonZeroRand64(rnd *rand.Rand) int64 {
+ for {
+ r := rnd.Int63()
+ if r == 0 {
+ continue
+ }
+ if rnd.Intn(1) != 0 {
+ return -r
+ }
+ return r
+ }
+}
+
+func NonZeroRand32(rnd *rand.Rand) int32 {
+ for {
+ r := rnd.Int31()
+ if r == 0 {
+ continue
+ }
+ if rnd.Intn(1) != 0 {
+ return -r
+ }
+ return r
+ }
+}
+
+// Create a random span.
+func NewRandomSpan(rnd *rand.Rand, potentialParents []*common.Span) *common.Span {
+ var parentId int64 = common.INVALID_SPAN_ID
+ if potentialParents != nil {
+ parentIdx := rnd.Intn(len(potentialParents) + 1)
+ if parentIdx < len(potentialParents) {
+ parentId = potentialParents[parentIdx].SpanId
+ }
+ }
+ return &common.Span{SpanId: NonZeroRand64(rnd),
+ SpanData: common.SpanData{
+ Start: NonZeroRand64(rnd),
+ Stop: NonZeroRand64(rnd),
+ Description: "getFileDescriptors",
+ TraceId: NonZeroRand64(rnd),
+ ParentId: parentId,
+ ProcessId: NonZeroRand32(rnd),
+ }}
+}