From f8a99109d00e2bc0718848241a462709ffa7f6eb Mon Sep 17 00:00:00 2001 From: candysmurf <77.ears@gmail.com> Date: Sat, 1 Oct 2016 11:24:02 -0700 Subject: [PATCH] SDI-1725:issue1234: RFC: Arbitrary namespace separator --- core/metric.go | 39 ++++++++++++++++++- core/metric_test.go | 64 +++++++++++++++++++++++++++++++ scheduler/wmap/sample/2.json | 35 +++++++++++++++++ scheduler/wmap/wmap.go | 17 +++++++- scheduler/wmap/wmap_small_test.go | 21 ++++++++++ 5 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 core/metric_test.go create mode 100644 scheduler/wmap/sample/2.json diff --git a/core/metric.go b/core/metric.go index 59f30380c..df7911f74 100644 --- a/core/metric.go +++ b/core/metric.go @@ -20,6 +20,7 @@ limitations under the License. package core import ( + "fmt" "strings" "time" @@ -31,6 +32,7 @@ var ( // Standard Tags are in added to the metric by the framework on plugin load. // STD_TAG_PLUGIN_RUNNING_ON describes where the plugin is running (hostname). STD_TAG_PLUGIN_RUNNING_ON = "plugin_running_on" + nsPriorityList = []string{"/", "|", "%", ":", "-", ";", "_", "^", ">", "<", "+", "=", "&", "㊽", "Ä", "大", "小", "ᵹ", "☍", "ヒ"} ) // Metric represents a snap metric collected or to be collected @@ -51,7 +53,8 @@ type Namespace []NamespaceElement // the elements of the namespace. A leading "/" is added. func (n Namespace) String() string { ns := n.Strings() - return "/" + strings.Join(ns, "/") + s := n.getSeparator() + return s + strings.Join(ns, s) } // Strings returns an array of strings that represent the elements of the @@ -64,6 +67,40 @@ func (n Namespace) Strings() []string { return ns } +// getSeparator returns the highest suitable separator from the nsPriorityList. +// Otherwise the core separator is returned. +func (n Namespace) getSeparator() string { + smap := initSeparatorMap() + + for _, e := range n { + // look at each char + for _, r := range e.Value { + ch := fmt.Sprintf("%c", r) + if v, ok := smap[ch]; ok && !v { + smap[ch] = true + } + } + } + + // Go through our separator list + for _, s := range nsPriorityList { + if v, ok := smap[s]; ok && !v { + return s + } + } + return Separator +} + +// initSeparatorMap populates the local map of nsPriorityList. +func initSeparatorMap() map[string]bool { + m := map[string]bool{} + + for _, s := range nsPriorityList { + m[s] = false + } + return m +} + // IsDynamic returns true if there is any element of the namespace which is // dynamic. If the namespace is dynamic the second return value will contain // an array of namespace elements (indexes) where there are dynamic namespace diff --git a/core/metric_test.go b/core/metric_test.go new file mode 100644 index 000000000..ce75e4468 --- /dev/null +++ b/core/metric_test.go @@ -0,0 +1,64 @@ +// +build small + +package core + +import ( + "fmt" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestMetricSeparator(t *testing.T) { + tc := getTestCases() + Convey("Test namespace separator", t, func() { + for _, c := range tc { + Convey("namespace "+c.input.String(), func() { + firstChar := getFirstChar(c.input.String()) + So(firstChar, ShouldEqual, c.expected) + }) + } + }) +} + +// GetFirstChar returns the first character from the input string. +func getFirstChar(s string) string { + firstChar := "" + for _, r := range s { + firstChar = fmt.Sprintf("%c", r) + break + } + return firstChar +} + +type testCase struct { + input Namespace + expected string +} + +// getTestCases tests the namespace and nsPriorityList. +func getTestCases() []testCase { + tcs := []testCase{ + testCase{ + input: NewNamespace("/hello", "/world"), + expected: "|", + }, + testCase{ + input: NewNamespace("/hello", "/world", "corporate-service|"), + expected: "%", + }, + testCase{ + input: NewNamespace("/hello", "/world", "|corporate-service%", "monday_to_friday"), + expected: ":", + }, + testCase{ + input: NewNamespace("/hello", "/world", "corporate-service/%|-_^><+=:;&", "monday_friday", "㊽ÄA小ヒ☍"), + expected: "大", + }, + testCase{ + input: NewNamespace("A小ヒ☍小㊽%:;", "/hello", "/world大|", "monday_friday", "corporate-service"), + expected: "^", + }, + } + return tcs +} diff --git a/scheduler/wmap/sample/2.json b/scheduler/wmap/sample/2.json new file mode 100644 index 000000000..a1bf5eaa8 --- /dev/null +++ b/scheduler/wmap/sample/2.json @@ -0,0 +1,35 @@ +{ + "collect": { + "metrics": { + "㊽foo㊽bar": { + "version": 1 + }, + "大foo大baz": {}, + "/a0/b0": {}, + "-a1-b1": {}, + "_a2_b2": {}, + "㊽a3㊽b3": {}, + "Äa4Äb4": {}, + "大a5大b5": {}, + "小a6小b6": {}, + "ᵹa7ᵹb7": {}, + "☍a8☍b8": {}, + "ヒa9ヒb9": {} + }, + "config": { + "%foo%bar": { + "password": "drowssap", + "user": "root" + } + }, + "tags": { + "㊽foo㊽bar": { + "tag1": "val1", + "tag2": "val2" + }, + "大foo大baz": { + "tag3": "val3" + } + } + } +} diff --git a/scheduler/wmap/wmap.go b/scheduler/wmap/wmap.go index cd10958fe..644764c3c 100644 --- a/scheduler/wmap/wmap.go +++ b/scheduler/wmap/wmap.go @@ -215,9 +215,12 @@ func (c *CollectWorkflowMapNode) GetMetrics() []Metric { metrics := make([]Metric, len(c.Metrics)) i := 0 for k, v := range c.Metrics { - ns := strings.Trim(k, `/`) + // Identify the character to split on by peaking + // at the first character of each metric. + firstChar := getFirstChar(k) + ns := strings.Trim(k, firstChar) metrics[i] = Metric{ - namespace: strings.Split(ns, "/"), + namespace: strings.Split(ns, firstChar), version: v.Version_, } i++ @@ -225,6 +228,16 @@ func (c *CollectWorkflowMapNode) GetMetrics() []Metric { return metrics } +// GetFirstChar returns the first character from the input string. +func getFirstChar(s string) string { + firstChar := "" + for _, r := range s { + firstChar = fmt.Sprintf("%c", r) + break + } + return firstChar +} + func (c *CollectWorkflowMapNode) GetTags() map[string]map[string]string { return c.Tags } diff --git a/scheduler/wmap/wmap_small_test.go b/scheduler/wmap/wmap_small_test.go index 4b19b6f31..abaa95461 100644 --- a/scheduler/wmap/wmap_small_test.go +++ b/scheduler/wmap/wmap_small_test.go @@ -22,6 +22,8 @@ limitations under the License. package wmap import ( + "io/ioutil" + "strconv" "testing" . "github.com/smartystreets/goconvey/convey" @@ -223,3 +225,22 @@ func TestStringByteConvertion(t *testing.T) { So(err, ShouldNotBeNil) }) } + +func TestMetricSeparator(t *testing.T) { + jsonP, _ := ioutil.ReadFile("./sample/2.json") + + Convey("Get Metric", t, func() { + Convey("from json", func() { + wmap, err := FromJson(jsonP) + So(err, ShouldBeNil) + So(wmap, ShouldNotBeNil) + + mts := wmap.CollectNode.GetMetrics() + for i, m := range mts { + Convey("namespace "+strconv.Itoa(i), func() { + So(len(m.Namespace()), ShouldEqual, 2) + }) + } + }) + }) +}