PMM-1057: let's try to bypass limitations of go/bson

This commit is contained in:
Kamil Dziedzic
2017-08-24 10:29:54 +02:00
parent 92ce29acfa
commit 04f95917ae
9 changed files with 104 additions and 57 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/percona/percona-toolkit/src/go/mongolib/proto" "github.com/percona/percona-toolkit/src/go/mongolib/proto"
"github.com/percona/percona-toolkit/src/go/mongolib/util" "github.com/percona/percona-toolkit/src/go/mongolib/util"
"gopkg.in/mgo.v2/bson"
) )
var ( var (
@@ -68,7 +69,7 @@ func (f *Fingerprint) Fingerprint(doc proto.SystemProfile) (string, error) {
// if there is a sort clause in the query, we have to add all fields in the sort // if there is a sort clause in the query, we have to add all fields in the sort
// fields list that are not in the query keys list (retKeys) // fields list that are not in the query keys list (retKeys)
if sortKeys, ok := query.Map()["sort"]; ok { if sortKeys, ok := query.Map()["sort"]; ok {
if sortKeysMap, ok := sortKeys.(map[string]interface{}); ok { if sortKeysMap, ok := sortKeys.(bson.M); ok {
sortKeys := keys(sortKeysMap, f.keyFilters) sortKeys := keys(sortKeysMap, f.keyFilters)
retKeys = append(retKeys, sortKeys...) retKeys = append(retKeys, sortKeys...)
} }
@@ -102,20 +103,20 @@ func (f *Fingerprint) Fingerprint(doc proto.SystemProfile) (string, error) {
break break
} }
// first key is operation type // first key is operation type
op = query.D[0].Name op = query[0].Name
collection, _ = query.D[0].Value.(string) collection, _ = query[0].Value.(string)
switch op { switch op {
case "group": case "group":
retKeys = []string{} retKeys = []string{}
if g, ok := query.Map()["group"]; ok { if g, ok := query.Map()["group"]; ok {
if m, ok := g.(map[string]interface{}); ok { if m, ok := g.(bson.M); ok {
if f, ok := m["key"]; ok { if f, ok := m["key"]; ok {
if keysMap, ok := f.(map[string]interface{}); ok { if keysMap, ok := f.(bson.M); ok {
retKeys = append(retKeys, keys(keysMap, []string{})...) retKeys = append(retKeys, keys(keysMap, []string{})...)
} }
} }
if f, ok := m["cond"]; ok { if f, ok := m["cond"]; ok {
if keysMap, ok := f.(map[string]interface{}); ok { if keysMap, ok := f.(bson.M); ok {
retKeys = append(retKeys, keys(keysMap, []string{})...) retKeys = append(retKeys, keys(keysMap, []string{})...)
} }
} }
@@ -167,18 +168,19 @@ func keys(query interface{}, keyFilters []string) []string {
func getKeys(query interface{}, keyFilters []string, level int) []string { func getKeys(query interface{}, keyFilters []string, level int) []string {
ks := []string{} ks := []string{}
var q []interface{} var q []bson.M
switch v := query.(type) { switch v := query.(type) {
case map[string]interface{}: case bson.M:
q = append(q, v) q = append(q, v)
case []interface{}: case []bson.M:
q = v q = v
default:
return ks
} }
if level <= MAX_DEPTH_LEVEL { if level <= MAX_DEPTH_LEVEL {
for i := range q { for i := range q {
if query, ok := q[i].(map[string]interface{}); ok { for key, value := range q[i] {
for key, value := range query {
if shouldSkipKey(key, keyFilters) { if shouldSkipKey(key, keyFilters) {
continue continue
} }
@@ -188,9 +190,6 @@ func getKeys(query interface{}, keyFilters []string, level int) []string {
ks = append(ks, getKeys(value, keyFilters, level)...) ks = append(ks, getKeys(value, keyFilters, level)...)
} }
} else {
ks = append(ks, getKeys(q[i], keyFilters, level)...)
}
} }
} }
return ks return ks

View File

@@ -50,15 +50,15 @@ func ExampleFingerprint() {
func TestFingerprint(t *testing.T) { func TestFingerprint(t *testing.T) {
doc := proto.SystemProfile{} doc := proto.SystemProfile{}
doc.Query = proto.BsonD{bson.D{ doc.Query = proto.BsonD{
{"find", "feedback"}, {"find", "feedback"},
{"filter", map[string]interface{}{ {"filter", bson.M{
"tool": "Atlas", "tool": "Atlas",
"potId": "2c9180865ae33e85015af1cc29243dc5", "potId": "2c9180865ae33e85015af1cc29243dc5",
}}, }},
{"limit", 1}, {"limit", 1},
{"singleBatch", true}, {"singleBatch", true},
}} }
want := "FIND feedback potId,tool" want := "FIND feedback potId,tool"
fp := NewFingerprinter(nil) fp := NewFingerprinter(nil)

View File

@@ -73,7 +73,7 @@ func TestRegularIterator(t *testing.T) {
ID: "16196765fb4c14edb91efdbe4f5c5973", ID: "16196765fb4c14edb91efdbe4f5c5973",
Namespace: "samples.col1", Namespace: "samples.col1",
Operation: "query", Operation: "query",
Query: "{\"find\":\"col1\",\"shardVersion\":[0,\"000000000000000000000000\"]}\n", Query: "{\n \"find\": \"col1\",\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
Fingerprint: "FIND col1 find", Fingerprint: "FIND col1 find",
FirstSeen: firstSeen, FirstSeen: firstSeen,
LastSeen: lastSeen, LastSeen: lastSeen,
@@ -130,7 +130,7 @@ func TestIteratorTimeout(t *testing.T) {
ID: "16196765fb4c14edb91efdbe4f5c5973", ID: "16196765fb4c14edb91efdbe4f5c5973",
Namespace: "samples.col1", Namespace: "samples.col1",
Operation: "query", Operation: "query",
Query: "{\"find\":\"col1\",\"shardVersion\":[0,\"000000000000000000000000\"]}\n", Query: "{\n \"find\": \"col1\",\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
Fingerprint: "FIND col1 find", Fingerprint: "FIND col1 find",
FirstSeen: firstSeen, FirstSeen: firstSeen,
LastSeen: lastSeen, LastSeen: lastSeen,
@@ -211,7 +211,7 @@ func TestTailIterator(t *testing.T) {
ID: "16196765fb4c14edb91efdbe4f5c5973", ID: "16196765fb4c14edb91efdbe4f5c5973",
Namespace: "samples.col1", Namespace: "samples.col1",
Operation: "query", Operation: "query",
Query: "{\"find\":\"col1\",\"shardVersion\":[0,\"000000000000000000000000\"]}\n", Query: "{\n \"find\": \"col1\",\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
Fingerprint: "FIND col1 find", Fingerprint: "FIND col1 find",
FirstSeen: parseDate("2017-04-01T23:01:20.214+00:00"), FirstSeen: parseDate("2017-04-01T23:01:20.214+00:00"),
LastSeen: parseDate("2017-04-01T23:01:20.214+00:00"), LastSeen: parseDate("2017-04-01T23:01:20.214+00:00"),
@@ -226,7 +226,7 @@ func TestTailIterator(t *testing.T) {
ID: "16196765fb4c14edb91efdbe4f5c5973", ID: "16196765fb4c14edb91efdbe4f5c5973",
Namespace: "samples.col1", Namespace: "samples.col1",
Operation: "query", Operation: "query",
Query: "{\"find\":\"col1\",\"shardVersion\":[0,\"000000000000000000000000\"]}\n", Query: "{\n \"find\": \"col1\",\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
Fingerprint: "FIND col1 find", Fingerprint: "FIND col1 find",
FirstSeen: parseDate("2017-04-01T23:01:19.914+00:00"), FirstSeen: parseDate("2017-04-01T23:01:19.914+00:00"),
LastSeen: parseDate("2017-04-01T23:01:19.914+00:00"), LastSeen: parseDate("2017-04-01T23:01:19.914+00:00"),
@@ -258,13 +258,13 @@ func TestCalcStats(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
docs := []proto.SystemProfile{} docs := []proto.SystemProfile{}
err := tutil.LoadJson(vars.RootPath+samples+"profiler_docs_stats.json", &docs) err := tutil.LoadBson(vars.RootPath+samples+"profiler_docs_stats.json", &docs)
if err != nil { if err != nil {
t.Fatalf("cannot load samples: %s", err.Error()) t.Fatalf("cannot load samples: %s", err.Error())
} }
want := []stats.QueryStats{} want := []stats.QueryStats{}
err = tutil.LoadJson(vars.RootPath+samples+"profiler_docs_stats.want.json", &want) err = tutil.LoadBson(vars.RootPath+samples+"profiler_docs_stats.want.json", &want)
if err != nil { if err != nil {
t.Fatalf("cannot load expected results: %s", err.Error()) t.Fatalf("cannot load expected results: %s", err.Error())
} }
@@ -307,13 +307,13 @@ func TestCalcTotalStats(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
docs := []proto.SystemProfile{} docs := []proto.SystemProfile{}
err := tutil.LoadJson(vars.RootPath+samples+"profiler_docs_stats.json", &docs) err := tutil.LoadBson(vars.RootPath+samples+"profiler_docs_stats.json", &docs)
if err != nil { if err != nil {
t.Fatalf("cannot load samples: %s", err.Error()) t.Fatalf("cannot load samples: %s", err.Error())
} }
want := stats.QueryStats{} want := stats.QueryStats{}
err = tutil.LoadJson(vars.RootPath+samples+"profiler_docs_total_stats.want.json", &want) err = tutil.LoadBson(vars.RootPath+samples+"profiler_docs_total_stats.want.json", &want)
if err != nil && !tutil.ShouldUpdateSamples() { if err != nil && !tutil.ShouldUpdateSamples() {
t.Fatalf("cannot load expected results: %s", err.Error()) t.Fatalf("cannot load expected results: %s", err.Error())
} }

View File

@@ -8,9 +8,7 @@ import (
"gopkg.in/mgo.v2/bson" "gopkg.in/mgo.v2/bson"
) )
type BsonD struct { type BsonD bson.D
bson.D
}
func (d *BsonD) UnmarshalJSON(data []byte) error { func (d *BsonD) UnmarshalJSON(data []byte) error {
dec := json.NewDecoder(bytes.NewReader(data)) dec := json.NewDecoder(bytes.NewReader(data))
@@ -45,19 +43,33 @@ func (d *BsonD) UnmarshalJSON(data []byte) error {
return fmt.Errorf("missing value for key %s", key) return fmt.Errorf("missing value for key %s", key)
} }
v := BsonD{} var raw json.RawMessage
r := dec.Buffered() err = dec.Decode(&raw)
ndec := json.NewDecoder(r) if err != nil {
err = ndec.Decode(&v) return err
}
var v BsonD
err = bson.UnmarshalJSON(raw, &v)
if err != nil {
var v []BsonD
err = bson.UnmarshalJSON(raw, &v)
if err != nil { if err != nil {
var v interface{} var v interface{}
dec.Decode(&v) err = bson.UnmarshalJSON(raw, &v)
if err != nil {
return err
} else {
de.Value = v de.Value = v
}
} else {
de.Value = v
}
} else { } else {
de.Value = v de.Value = v
} }
d.D = append(d.D, de) *d = append(*d, de)
if !dec.More() { if !dec.More() {
break break
} }
@@ -74,12 +86,12 @@ func (d *BsonD) UnmarshalJSON(data []byte) error {
return nil return nil
} }
func (d *BsonD) MarshalJSON() ([]byte, error) { func (d BsonD) MarshalJSON() ([]byte, error) {
var b bytes.Buffer var b bytes.Buffer
b.WriteByte('{') b.WriteByte('{')
for i, v := range d.D { for i, v := range d {
if i > 0 { if i > 0 {
b.WriteByte(',') b.WriteByte(',')
} }
@@ -106,5 +118,35 @@ func (d *BsonD) MarshalJSON() ([]byte, error) {
} }
func (d BsonD) Len() int { func (d BsonD) Len() int {
return len(d.D) return len(d)
}
// Map returns a map out of the ordered element name/value pairs in d.
func (d BsonD) Map() (m bson.M) {
m = make(bson.M, len(d))
for _, item := range d {
switch v := item.Value.(type) {
case BsonD:
m[item.Name] = v.Map()
case []BsonD:
el := []bson.M{}
for i := range v {
el = append(el, v[i].Map())
}
m[item.Name] = el
case []interface{}:
// mgo/bson doesn't expose UnmarshalBSON interface
// so we can't create custom bson.Unmarshal()
el := []bson.M{}
for i := range v {
if b, ok := v[i].(BsonD); ok {
el = append(el, b.Map())
}
}
m[item.Name] = el
default:
m[item.Name] = item.Value
}
}
return m
} }

View File

@@ -6,11 +6,11 @@ import (
"sort" "sort"
"sync" "sync"
"time" "time"
"encoding/json"
"github.com/montanaflynn/stats" "github.com/montanaflynn/stats"
"github.com/percona/percona-toolkit/src/go/mongolib/fingerprinter" "github.com/percona/percona-toolkit/src/go/mongolib/fingerprinter"
"github.com/percona/percona-toolkit/src/go/mongolib/proto" "github.com/percona/percona-toolkit/src/go/mongolib/proto"
"gopkg.in/mgo.v2/bson"
) )
type StatsError struct { type StatsError struct {
@@ -82,7 +82,7 @@ func (s *Stats) Add(doc proto.SystemProfile) error {
if err != nil { if err != nil {
return &StatsGetQueryFieldError{err} return &StatsGetQueryFieldError{err}
} }
queryBson, err := bson.MarshalJSON(&query) queryBson, err := json.MarshalIndent(query, "", " ")
if err != nil { if err != nil {
return err return err
} }

View File

@@ -142,7 +142,7 @@ func TestStats(t *testing.T) {
ID: "4e4774ad26f934a193757002a991ebb8", ID: "4e4774ad26f934a193757002a991ebb8",
Namespace: "samples.col1", Namespace: "samples.col1",
Operation: "query", Operation: "query",
Query: "{\"find\":\"col1\",\"filter\":{\"s2\":{\"$gte\":\"41991\",\"$lt\":\"33754\"}},\"shardVersion\":[0,\"000000000000000000000000\"]}\n", Query: "{\n \"find\": \"col1\",\n \"filter\": {\n \"s2\": {\n \"$gte\": \"41991\",\n \"$lt\": \"33754\"\n }\n },\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
Fingerprint: "FIND col1 s2", Fingerprint: "FIND col1 s2",
FirstSeen: parseDate("2017-04-10T13:15:53.532-03:00"), FirstSeen: parseDate("2017-04-10T13:15:53.532-03:00"),
LastSeen: parseDate("2017-04-10T13:15:53.532-03:00"), LastSeen: parseDate("2017-04-10T13:15:53.532-03:00"),

View File

@@ -237,7 +237,7 @@ func GetServerStatus(dialer pmgo.Dialer, di *pmgo.DialInfo, hostname string) (pr
return ss, nil return ss, nil
} }
func GetQueryField(doc proto.SystemProfile) (map[string]interface{}, error) { func GetQueryField(doc proto.SystemProfile) (bson.M, error) {
// Proper way to detect if protocol used is "op_msg" or "op_command" // Proper way to detect if protocol used is "op_msg" or "op_command"
// would be to look at "doc.Protocol" field, // would be to look at "doc.Protocol" field,
// however MongoDB 3.0 doesn't have that field // however MongoDB 3.0 doesn't have that field
@@ -248,7 +248,7 @@ func GetQueryField(doc proto.SystemProfile) (map[string]interface{}, error) {
if doc.Op == "update" || doc.Op == "remove" { if doc.Op == "update" || doc.Op == "remove" {
if squery, ok := query.Map()["q"]; ok { if squery, ok := query.Map()["q"]; ok {
// just an extra check to ensure this type assertion won't fail // just an extra check to ensure this type assertion won't fail
if ssquery, ok := squery.(map[string]interface{}); ok { if ssquery, ok := squery.(bson.M); ok {
return ssquery, nil return ssquery, nil
} }
return nil, CANNOT_GET_QUERY_ERROR return nil, CANNOT_GET_QUERY_ERROR
@@ -318,7 +318,7 @@ func GetQueryField(doc proto.SystemProfile) (map[string]interface{}, error) {
// //
if squery, ok := query.Map()["query"]; ok { if squery, ok := query.Map()["query"]; ok {
// just an extra check to ensure this type assertion won't fail // just an extra check to ensure this type assertion won't fail
if ssquery, ok := squery.(map[string]interface{}); ok { if ssquery, ok := squery.(bson.M); ok {
return ssquery, nil return ssquery, nil
} }
return nil, CANNOT_GET_QUERY_ERROR return nil, CANNOT_GET_QUERY_ERROR
@@ -326,7 +326,7 @@ func GetQueryField(doc proto.SystemProfile) (map[string]interface{}, error) {
// "query" in MongoDB 3.2+ is better structured and always has a "filter" subkey: // "query" in MongoDB 3.2+ is better structured and always has a "filter" subkey:
if squery, ok := query.Map()["filter"]; ok { if squery, ok := query.Map()["filter"]; ok {
if ssquery, ok := squery.(map[string]interface{}); ok { if ssquery, ok := squery.(bson.M); ok {
return ssquery, nil return ssquery, nil
} }
return nil, CANNOT_GET_QUERY_ERROR return nil, CANNOT_GET_QUERY_ERROR

View File

@@ -1,4 +1,10 @@
var coll = db.coll var coll = db.coll
coll.createIndex({a: 1}) coll.createIndex({a: 1})
coll.find({a: 1})
var i;
for (i = 0; i < 10; ++i) {
coll.insert({a: i % 5});
}
coll.find({a: 1}).pretty()

View File

@@ -3,7 +3,7 @@
"ID": "16196765fb4c14edb91efdbe4f5c5973", "ID": "16196765fb4c14edb91efdbe4f5c5973",
"Namespace": "samples.col1", "Namespace": "samples.col1",
"Operation": "query", "Operation": "query",
"Query": "{\"find\":\"col1\",\"shardVersion\":[0,\"000000000000000000000000\"]}\n", "Query": "{\n \"find\": \"col1\",\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
"Fingerprint": "FIND col1 find", "Fingerprint": "FIND col1 find",
"FirstSeen": "2017-04-10T13:16:23.29-03:00", "FirstSeen": "2017-04-10T13:16:23.29-03:00",
"LastSeen": "2017-04-10T13:16:23.29-03:00", "LastSeen": "2017-04-10T13:16:23.29-03:00",
@@ -56,7 +56,7 @@
"ID": "4e4774ad26f934a193757002a991ebb8", "ID": "4e4774ad26f934a193757002a991ebb8",
"Namespace": "samples.col1", "Namespace": "samples.col1",
"Operation": "query", "Operation": "query",
"Query": "{\"find\":\"col1\",\"filter\":{\"s2\":{\"$gte\":\"41991\",\"$lt\":\"33754\"}},\"shardVersion\":[0,\"000000000000000000000000\"]}\n", "Query": "{\n \"find\": \"col1\",\n \"filter\": {\n \"s2\": {\n \"$gte\": \"41991\",\n \"$lt\": \"33754\"\n }\n },\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ]\n}",
"Fingerprint": "FIND col1 s2", "Fingerprint": "FIND col1 s2",
"FirstSeen": "2017-04-10T13:15:53.532-03:00", "FirstSeen": "2017-04-10T13:15:53.532-03:00",
"LastSeen": "2017-04-10T13:15:53.532-03:00", "LastSeen": "2017-04-10T13:15:53.532-03:00",
@@ -109,7 +109,7 @@
"ID": "8cb8666ace7e54767b4d3c4f61860cf9", "ID": "8cb8666ace7e54767b4d3c4f61860cf9",
"Namespace": "samples.col1", "Namespace": "samples.col1",
"Operation": "query", "Operation": "query",
"Query": "{\"find\":\"col1\",\"filter\":{\"user_id\":{\"$gte\":3384024924,\"$lt\":195092007}},\"projection\":{\"$sortKey\":{\"$meta\":\"sortKey\"}},\"shardVersion\":[0,\"000000000000000000000000\"],\"sort\":{\"user_id\":1}}\n", "Query": "{\n \"find\": \"col1\",\n \"filter\": {\n \"user_id\": {\n \"$gte\": 3384024924,\n \"$lt\": 195092007\n }\n },\n \"projection\": {\n \"$sortKey\": {\n \"$meta\": \"sortKey\"\n }\n },\n \"shardVersion\": [\n 0,\n \"000000000000000000000000\"\n ],\n \"sort\": {\n \"user_id\": 1\n }\n}",
"Fingerprint": "FIND col1 user_id", "Fingerprint": "FIND col1 user_id",
"FirstSeen": "2017-04-10T13:15:53.524-03:00", "FirstSeen": "2017-04-10T13:15:53.524-03:00",
"LastSeen": "2017-04-10T13:15:53.524-03:00", "LastSeen": "2017-04-10T13:15:53.524-03:00",