From 82b5b6c68f3ecd5bd5e38429bf753fed8cad0704 Mon Sep 17 00:00:00 2001 From: Xi Chen Date: Thu, 6 Apr 2017 14:37:42 -0400 Subject: [PATCH] Add unit tests --- generated/proto/rule.proto | 2 +- rules/mapping.go | 28 +- rules/mapping_test.go | 142 ++++ rules/namespace.go | 12 +- rules/namespace_test.go | 83 ++ rules/result.go | 2 +- rules/result_test.go | 94 +++ rules/rollup.go | 32 +- rules/rollup_test.go | 246 ++++++ rules/ruleset.go | 53 +- rules/ruleset_test.go | 1486 ++++++++++++++++++++++++++++++------ 11 files changed, 1877 insertions(+), 303 deletions(-) create mode 100644 rules/namespace_test.go create mode 100644 rules/result_test.go create mode 100644 rules/rollup_test.go diff --git a/generated/proto/rule.proto b/generated/proto/rule.proto index eff783e..6241665 100644 --- a/generated/proto/rule.proto +++ b/generated/proto/rule.proto @@ -54,4 +54,4 @@ message Namespace { message Namespaces { repeated Namespace namespaces = 1; -} \ No newline at end of file +} diff --git a/rules/mapping.go b/rules/mapping.go index 161511b..a85e0a8 100644 --- a/rules/mapping.go +++ b/rules/mapping.go @@ -22,7 +22,6 @@ package rules import ( "errors" - "time" "github.com/m3db/m3metrics/filters" "github.com/m3db/m3metrics/generated/proto/schema" @@ -46,7 +45,7 @@ type mappingRuleSnapshot struct { func newMappingRuleSnapshot( r *schema.MappingRuleSnapshot, - iterfn filters.NewSortedTagIteratorFn, + iterFn filters.NewSortedTagIteratorFn, ) (*mappingRuleSnapshot, error) { if r == nil { return nil, errNilMappingRuleSnapshotSchema @@ -55,7 +54,7 @@ func newMappingRuleSnapshot( if err != nil { return nil, err } - filter, err := filters.NewTagsFilter(r.TagFilters, iterfn, filters.Conjunction) + filter, err := filters.NewTagsFilter(r.TagFilters, iterFn, filters.Conjunction) if err != nil { return nil, err } @@ -76,14 +75,14 @@ type mappingRule struct { func newMappingRule( mc *schema.MappingRule, - iterfn filters.NewSortedTagIteratorFn, + iterFn filters.NewSortedTagIteratorFn, ) (*mappingRule, error) { if mc == nil { return nil, errNilMappingRuleSchema } snapshots := make([]*mappingRuleSnapshot, 0, len(mc.Snapshots)) for i := 0; i < len(mc.Snapshots); i++ { - mr, err := newMappingRuleSnapshot(mc.Snapshots[i], iterfn) + mr, err := newMappingRuleSnapshot(mc.Snapshots[i], iterFn) if err != nil { return nil, err } @@ -96,19 +95,19 @@ func newMappingRule( } // ActiveSnapshot returns the latest snapshot whose cutover time is earlier than or -// equal to t, or nil if not found. -func (mc *mappingRule) ActiveSnapshot(t time.Time) *mappingRuleSnapshot { - idx := mc.activeIndex(t) +// equal to timeNs, or nil if not found. +func (mc *mappingRule) ActiveSnapshot(timeNs int64) *mappingRuleSnapshot { + idx := mc.activeIndex(timeNs) if idx < 0 { return nil } return mc.snapshots[idx] } -// ActiveRule returns the rule containing snapshots that's in effect at time t and -// all future snapshots after time t. -func (mc *mappingRule) ActiveRule(t time.Time) *mappingRule { - idx := mc.activeIndex(t) +// ActiveRule returns the rule containing snapshots that's in effect at time timeNs +// and all future snapshots after time timeNs. +func (mc *mappingRule) ActiveRule(timeNs int64) *mappingRule { + idx := mc.activeIndex(timeNs) // If there are no snapshots that are currently in effect, it means either all // snapshots are in the future, or there are no snapshots. if idx < 0 { @@ -117,10 +116,9 @@ func (mc *mappingRule) ActiveRule(t time.Time) *mappingRule { return &mappingRule{uuid: mc.uuid, snapshots: mc.snapshots[idx:]} } -func (mc *mappingRule) activeIndex(t time.Time) int { - target := t.UnixNano() +func (mc *mappingRule) activeIndex(timeNs int64) int { idx := 0 - for idx < len(mc.snapshots) && mc.snapshots[idx].cutoverNs <= target { + for idx < len(mc.snapshots) && mc.snapshots[idx].cutoverNs <= timeNs { idx++ } idx-- diff --git a/rules/mapping_test.go b/rules/mapping_test.go index 1bfefc3..c5a1e91 100644 --- a/rules/mapping_test.go +++ b/rules/mapping_test.go @@ -19,3 +19,145 @@ // THE SOFTWARE. package rules + +import ( + "testing" + "time" + + "github.com/m3db/m3metrics/generated/proto/schema" + "github.com/m3db/m3metrics/policy" + "github.com/m3db/m3x/time" + + "github.com/stretchr/testify/require" +) + +var ( + testMappingRuleSchema = &schema.MappingRule{ + Uuid: "12669817-13ae-40e6-ba2f-33087b262c68", + Snapshots: []*schema.MappingRuleSnapshot{ + &schema.MappingRuleSnapshot{ + Name: "foo", + Tombstoned: false, + CutoverTime: 12345, + TagFilters: map[string]string{ + "tag1": "value1", + "tag2": "value2", + }, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + }, + }, + &schema.MappingRuleSnapshot{ + Name: "bar", + Tombstoned: true, + CutoverTime: 67890, + TagFilters: map[string]string{ + "tag3": "value3", + "tag4": "value4", + }, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(5 * time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(48 * time.Hour), + }, + }, + }, + }, + }, + } +) + +func TestMappingRuleSnapshotNilSchema(t *testing.T) { + _, err := newMappingRuleSnapshot(nil, nil) + require.Equal(t, err, errNilMappingRuleSnapshotSchema) +} + +func TestNewMappingRuleNilSchema(t *testing.T) { + _, err := newMappingRule(nil, nil) + require.Equal(t, err, errNilMappingRuleSchema) +} + +func TestNewMappingRule(t *testing.T) { + mr, err := newMappingRule(testMappingRuleSchema, nil) + require.NoError(t, err) + require.Equal(t, "12669817-13ae-40e6-ba2f-33087b262c68", mr.uuid) + expectedSnapshots := []struct { + name string + tombstoned bool + cutoverNs int64 + policies []policy.Policy + }{ + { + name: "foo", + tombstoned: false, + cutoverNs: 12345, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + { + name: "bar", + tombstoned: true, + cutoverNs: 67890, + policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + } + for i, snapshot := range expectedSnapshots { + require.Equal(t, snapshot.name, mr.snapshots[i].name) + require.Equal(t, snapshot.tombstoned, mr.snapshots[i].tombstoned) + require.Equal(t, snapshot.cutoverNs, mr.snapshots[i].cutoverNs) + require.Equal(t, snapshot.policies, mr.snapshots[i].policies) + } +} + +func TestMappingRuleActiveSnapshotNotFound(t *testing.T) { + mr, err := newMappingRule(testMappingRuleSchema, nil) + require.NoError(t, err) + require.Nil(t, mr.ActiveSnapshot(0)) +} + +func TestMappingRuleActiveSnapshotFound(t *testing.T) { + mr, err := newMappingRule(testMappingRuleSchema, nil) + require.NoError(t, err) + require.Equal(t, mr.snapshots[1], mr.ActiveSnapshot(100000)) +} + +func TestMappingRuleActiveRuleNotFound(t *testing.T) { + mr, err := newMappingRule(testMappingRuleSchema, nil) + require.NoError(t, err) + require.Equal(t, mr, mr.ActiveRule(0)) +} + +func TestMappingRuleActiveRuleFound(t *testing.T) { + mr, err := newMappingRule(testMappingRuleSchema, nil) + require.NoError(t, err) + expected := &mappingRule{ + uuid: mr.uuid, + snapshots: mr.snapshots[1:], + } + require.Equal(t, expected, mr.ActiveRule(100000)) +} diff --git a/rules/namespace.go b/rules/namespace.go index 2da8ea5..96d3638 100644 --- a/rules/namespace.go +++ b/rules/namespace.go @@ -36,7 +36,7 @@ var ( // Namespace is a logical isolation unit for which rules are defined. type Namespace struct { - name string + name []byte tombstoned bool expireAtNs int64 } @@ -47,14 +47,14 @@ func newNameSpace(namespace *schema.Namespace) (Namespace, error) { return emptyNamespace, errNilNamespaceSchema } return Namespace{ - name: namespace.Name, + name: []byte(namespace.Name), tombstoned: namespace.Tombstoned, expireAtNs: namespace.ExpireAt, }, nil } // Name is the name of the namespace. -func (n *Namespace) Name() string { return n.name } +func (n *Namespace) Name() []byte { return n.name } // Tombstoned determines whether the namespace has been tombstoned. func (n *Namespace) Tombstoned() bool { return n.tombstoned } @@ -87,8 +87,8 @@ func NewNamespaces(version int, namespaces *schema.Namespaces) (Namespaces, erro }, nil } -// Namespaces returns the list of namespaces. -func (nss Namespaces) Namespaces() []Namespace { return nss.namespaces } - // Version returns the namespaces version. func (nss Namespaces) Version() int { return nss.version } + +// Namespaces returns the list of namespaces. +func (nss Namespaces) Namespaces() []Namespace { return nss.namespaces } diff --git a/rules/namespace_test.go b/rules/namespace_test.go new file mode 100644 index 0000000..3a57d14 --- /dev/null +++ b/rules/namespace_test.go @@ -0,0 +1,83 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package rules + +import ( + "testing" + + "github.com/m3db/m3metrics/generated/proto/schema" + + "github.com/stretchr/testify/require" +) + +func TestNewNamespaceNilSchema(t *testing.T) { + _, err := newNameSpace(nil) + require.Equal(t, err, errNilNamespaceSchema) +} + +func TestNewNamespaceValidSchema(t *testing.T) { + ns, err := newNameSpace(&schema.Namespace{ + Name: "foo", + Tombstoned: false, + ExpireAt: 12345, + }) + require.NoError(t, err) + require.Equal(t, []byte("foo"), ns.Name()) + require.Equal(t, false, ns.Tombstoned()) + require.Equal(t, int64(12345), ns.ExpireAtNs()) +} + +func TestNewNamespacesNilSchema(t *testing.T) { + _, err := NewNamespaces(1, nil) + require.Equal(t, errNilNamespacesSchema, err) +} + +func TestNewNamespacesValidSchema(t *testing.T) { + ns, err := NewNamespaces(1, &schema.Namespaces{ + Namespaces: []*schema.Namespace{ + &schema.Namespace{ + Name: "foo", + Tombstoned: false, + ExpireAt: 12345, + }, + &schema.Namespace{ + Name: "bar", + Tombstoned: true, + ExpireAt: 67890, + }, + }, + }) + require.NoError(t, err) + require.Equal(t, 1, ns.Version()) + expected := []Namespace{ + { + name: b("foo"), + tombstoned: false, + expireAtNs: 12345, + }, + { + name: b("bar"), + tombstoned: true, + expireAtNs: 67890, + }, + } + require.Equal(t, expected, ns.Namespaces()) +} diff --git a/rules/result.go b/rules/result.go index 5170161..c2205f7 100644 --- a/rules/result.go +++ b/rules/result.go @@ -59,7 +59,7 @@ func newMatchResult( } // HasExpired returns whether the match result has expired for a given time. -func (r *MatchResult) HasExpired(t time.Time) bool { return r.expireAtNs >= t.UnixNano() } +func (r *MatchResult) HasExpired(t time.Time) bool { return r.expireAtNs <= t.UnixNano() } // NumRollups returns the number of rollup result associated with the given id. func (r *MatchResult) NumRollups() int { return len(r.rollups) } diff --git a/rules/result_test.go b/rules/result_test.go new file mode 100644 index 0000000..2e3ac7a --- /dev/null +++ b/rules/result_test.go @@ -0,0 +1,94 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package rules + +import ( + "testing" + "time" + + "github.com/m3db/m3metrics/policy" + "github.com/m3db/m3x/time" + + "github.com/stretchr/testify/require" +) + +func TestMatchResult(t *testing.T) { + var ( + version = 1 + cutoverNs = int64(12345) + expireAtNs = int64(67890) + mappings = []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + } + rollups = []rollupResult{ + { + ID: b("rName1|rtagName1=rtagValue1,rtagName2=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + { + ID: b("rName2|rtagName1=rtagValue1"), + Policies: []policy.Policy{}, + }, + } + ) + + res := newMatchResult(version, cutoverNs, expireAtNs, mappings, rollups) + require.False(t, res.HasExpired(time.Unix(0, 0))) + require.True(t, res.HasExpired(time.Unix(0, 100000))) + + expectedMappings := policy.CustomVersionedPolicies(version, time.Unix(0, 12345), mappings) + require.Equal(t, expectedMappings, res.Mappings()) + + var ( + expectedRollupIDs = [][]byte{ + b("rName1|rtagName1=rtagValue1,rtagName2=rtagValue2"), + b("rName2|rtagName1=rtagValue1"), + } + expectedRollupPolicies = []policy.VersionedPolicies{ + policy.CustomVersionedPolicies( + 1, + time.Unix(0, 12345), + []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + ), + policy.DefaultVersionedPolicies(1, time.Unix(0, 12345)), + } + rollupIDs [][]byte + rollupPolicies []policy.VersionedPolicies + ) + require.Equal(t, 2, res.NumRollups()) + for i := 0; i < 2; i++ { + id, policies := res.Rollups(i) + rollupIDs = append(rollupIDs, id) + rollupPolicies = append(rollupPolicies, policies) + } + require.Equal(t, expectedRollupIDs, rollupIDs) + require.Equal(t, expectedRollupPolicies, rollupPolicies) +} diff --git a/rules/rollup.go b/rules/rollup.go index 405dff3..35fcbba 100644 --- a/rules/rollup.go +++ b/rules/rollup.go @@ -24,7 +24,6 @@ import ( "bytes" "errors" "sort" - "time" "github.com/m3db/m3metrics/filters" "github.com/m3db/m3metrics/generated/proto/schema" @@ -84,10 +83,12 @@ func (t *rollupTarget) sameTransform(other rollupTarget) bool { // clone clones a rollup target. func (t *rollupTarget) clone() rollupTarget { + name := make([]byte, len(t.Name)) + copy(name, t.Name) policies := make([]policy.Policy, len(t.Policies)) copy(policies, t.Policies) return rollupTarget{ - Name: t.Name, + Name: name, Tags: bytesArrayCopy(t.Tags), Policies: policies, } @@ -105,7 +106,7 @@ type rollupRuleSnapshot struct { func newRollupRuleSnapshot( r *schema.RollupRuleSnapshot, - iterfn filters.NewSortedTagIteratorFn, + iterFn filters.NewSortedTagIteratorFn, ) (*rollupRuleSnapshot, error) { if r == nil { return nil, errNilRollupRuleSnapshotSchema @@ -118,7 +119,7 @@ func newRollupRuleSnapshot( } targets = append(targets, target) } - filter, err := filters.NewTagsFilter(r.TagFilters, iterfn, filters.Conjunction) + filter, err := filters.NewTagsFilter(r.TagFilters, iterFn, filters.Conjunction) if err != nil { return nil, err } @@ -139,14 +140,14 @@ type rollupRule struct { func newRollupRule( mc *schema.RollupRule, - iterfn filters.NewSortedTagIteratorFn, + iterFn filters.NewSortedTagIteratorFn, ) (*rollupRule, error) { if mc == nil { return nil, errNilRollupRuleSchema } snapshots := make([]*rollupRuleSnapshot, 0, len(mc.Snapshots)) for i := 0; i < len(mc.Snapshots); i++ { - mr, err := newRollupRuleSnapshot(mc.Snapshots[i], iterfn) + mr, err := newRollupRuleSnapshot(mc.Snapshots[i], iterFn) if err != nil { return nil, err } @@ -159,29 +160,28 @@ func newRollupRule( } // ActiveSnapshot returns the latest rule snapshot whose cutover time is earlier -// than or equal to t, or nil if not found. -func (rc *rollupRule) ActiveSnapshot(t time.Time) *rollupRuleSnapshot { - idx := rc.activeIndex(t) +// than or equal to timeNs, or nil if not found. +func (rc *rollupRule) ActiveSnapshot(timeNs int64) *rollupRuleSnapshot { + idx := rc.activeIndex(timeNs) if idx < 0 { return nil } return rc.snapshots[idx] } -// ActiveRule returns the rule containing snapshots that's in effect at time t -// and all future rules after time t. -func (rc *rollupRule) ActiveRule(t time.Time) *rollupRule { - idx := rc.activeIndex(t) +// ActiveRule returns the rule containing snapshots that's in effect at time timeNs +// and all future rules after time timeNs. +func (rc *rollupRule) ActiveRule(timeNs int64) *rollupRule { + idx := rc.activeIndex(timeNs) if idx < 0 { return rc } return &rollupRule{uuid: rc.uuid, snapshots: rc.snapshots[idx:]} } -func (rc *rollupRule) activeIndex(t time.Time) int { - target := t.UnixNano() +func (rc *rollupRule) activeIndex(timeNs int64) int { idx := 0 - for idx < len(rc.snapshots) && rc.snapshots[idx].cutoverNs <= target { + for idx < len(rc.snapshots) && rc.snapshots[idx].cutoverNs <= timeNs { idx++ } idx-- diff --git a/rules/rollup_test.go b/rules/rollup_test.go new file mode 100644 index 0000000..c9ce194 --- /dev/null +++ b/rules/rollup_test.go @@ -0,0 +1,246 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package rules + +import ( + "testing" + "time" + + "github.com/m3db/m3metrics/generated/proto/schema" + "github.com/m3db/m3metrics/policy" + "github.com/m3db/m3x/time" + + "github.com/stretchr/testify/require" +) + +var ( + testRollupRuleSchema = &schema.RollupRule{ + Uuid: "12669817-13ae-40e6-ba2f-33087b262c68", + Snapshots: []*schema.RollupRuleSnapshot{ + &schema.RollupRuleSnapshot{ + Name: "foo", + Tombstoned: false, + CutoverTime: 12345, + TagFilters: map[string]string{ + "tag1": "value1", + "tag2": "value2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + }, + }, + }, + }, + &schema.RollupRuleSnapshot{ + Name: "bar", + Tombstoned: true, + CutoverTime: 67890, + TagFilters: map[string]string{ + "tag3": "value3", + "tag4": "value4", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(5 * time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(48 * time.Hour), + }, + }, + }, + }, + }, + }, + }, + } +) + +func TestNewRollupTargetNilTargetSchema(t *testing.T) { + _, err := newRollupTarget(nil) + require.Equal(t, errNilRollupTargetSchema, err) +} + +func TestNewRollupTargetNilPolicySchema(t *testing.T) { + _, err := newRollupTarget(&schema.RollupTarget{ + Policies: []*schema.Policy{nil}, + }) + require.Error(t, err) +} + +func TestRollupTargetSameTransform(t *testing.T) { + policies := []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*24*time.Hour), + } + target := rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar2")} + inputs := []testRollupTargetData{ + { + target: rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar2"), Policies: policies}, + result: true, + }, + { + target: rollupTarget{Name: b("baz"), Tags: bs("bar1", "bar2")}, + result: false, + }, + { + target: rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar3")}, + result: false, + }, + } + for _, input := range inputs { + require.Equal(t, input.result, target.sameTransform(input.target)) + } +} + +func TestRollupTargetClone(t *testing.T) { + policies := []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*24*time.Hour), + } + target := rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar2"), Policies: policies} + cloned := target.clone() + + // Cloned object should look exactly the same as the original one + require.Equal(t, target, cloned) + + // Change references in the cloned object should not mutate the original object + cloned.Tags[0] = b("bar3") + cloned.Policies[0] = policy.EmptyPolicy + require.Equal(t, target.Tags, bs("bar1", "bar2")) + require.Equal(t, target.Policies, policies) +} + +func TestRollupRuleSnapshotNilSchema(t *testing.T) { + _, err := newRollupRuleSnapshot(nil, nil) + require.Equal(t, errNilRollupRuleSnapshotSchema, err) +} + +func TestRollupRuleNilSchema(t *testing.T) { + _, err := newRollupRule(nil, nil) + require.Equal(t, errNilRollupRuleSchema, err) +} + +func TestRollupRuleValidSchema(t *testing.T) { + rr, err := newRollupRule(testRollupRuleSchema, nil) + require.NoError(t, err) + require.Equal(t, "12669817-13ae-40e6-ba2f-33087b262c68", rr.uuid) + + expectedSnapshots := []struct { + name string + tombstoned bool + cutoverNs int64 + targets []rollupTarget + }{ + { + name: "foo", + tombstoned: false, + cutoverNs: 12345, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + }, + }, + { + name: "bar", + tombstoned: true, + cutoverNs: 67890, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + }, + }, + } + for i, snapshot := range expectedSnapshots { + require.Equal(t, snapshot.name, rr.snapshots[i].name) + require.Equal(t, snapshot.tombstoned, rr.snapshots[i].tombstoned) + require.Equal(t, snapshot.cutoverNs, rr.snapshots[i].cutoverNs) + require.Equal(t, snapshot.targets, rr.snapshots[i].targets) + } +} + +func TestRollupRuleActiveSnapshotNotFound(t *testing.T) { + rr, err := newRollupRule(testRollupRuleSchema, nil) + require.NoError(t, err) + require.Nil(t, rr.ActiveSnapshot(0)) +} + +func TestRollupRuleActiveSnapshotFound(t *testing.T) { + rr, err := newRollupRule(testRollupRuleSchema, nil) + require.NoError(t, err) + require.Equal(t, rr.snapshots[1], rr.ActiveSnapshot(100000)) +} + +func TestRollupRuleActiveRuleNotFound(t *testing.T) { + rr, err := newRollupRule(testRollupRuleSchema, nil) + require.NoError(t, err) + require.Equal(t, rr, rr.ActiveRule(0)) +} + +func TestRollupRuleActiveRuleFound(t *testing.T) { + rr, err := newRollupRule(testRollupRuleSchema, nil) + require.NoError(t, err) + expected := &rollupRule{ + uuid: rr.uuid, + snapshots: rr.snapshots[1:], + } + require.Equal(t, expected, rr.ActiveRule(100000)) +} + +type testRollupTargetData struct { + target rollupTarget + result bool +} diff --git a/rules/ruleset.go b/rules/ruleset.go index 1b91561..3e8f941 100644 --- a/rules/ruleset.go +++ b/rules/ruleset.go @@ -32,12 +32,12 @@ import ( "github.com/m3db/m3metrics/policy" ) +const ( + timeNsMax = int64(math.MaxInt64) +) + var ( errNilRuleSetSchema = errors.New("nil rule set schema") - - // timeMax is the maximum time value for which time.Before() and - // time.After() still work. - timeMax = time.Unix(1<<63-62135596801, 999999999).UnixNano() ) // TagPair contains a tag name and a tag value. @@ -97,46 +97,50 @@ func newActiveRuleSet( } func (as *activeRuleSet) Match(id []byte, t time.Time) MatchResult { - mappingCutoverNs, mappingPolicies := as.matchMappings(id, t) - rollupCutoverNs, rollupResults := as.matchRollups(id, t) + timeNs := t.UnixNano() + mappingCutoverNs, mappingPolicies := as.matchMappings(id, timeNs) + rollupCutoverNs, rollupResults := as.matchRollups(id, timeNs) // NB(xichen): we take the latest cutover time across all rules matched. This is // used to determine whether the match result is valid for a given time. cutoverNs := int64(math.Max(float64(mappingCutoverNs), float64(rollupCutoverNs))) - expireAtNs := as.nextCutover(cutoverNs) + + // The result expires when it reaches the first cutover time after t among all + // active rules because the metric may then be matched against a different set of rules. + expireAtNs := as.nextCutover(timeNs) return newMatchResult(as.version, cutoverNs, expireAtNs, mappingPolicies, rollupResults) } -func (as *activeRuleSet) matchMappings(id []byte, t time.Time) (int64, []policy.Policy) { +func (as *activeRuleSet) matchMappings(id []byte, timeNs int64) (int64, []policy.Policy) { // TODO(xichen): pool the policies var ( cutoverNs int64 policies []policy.Policy ) for _, mappingRule := range as.mappingRules { - snapshot := mappingRule.ActiveSnapshot(t) + snapshot := mappingRule.ActiveSnapshot(timeNs) if snapshot == nil { continue } - if cutoverNs < snapshot.cutoverNs { - cutoverNs = snapshot.cutoverNs - } if !snapshot.filter.Matches(id) { continue } + if cutoverNs < snapshot.cutoverNs { + cutoverNs = snapshot.cutoverNs + } policies = append(policies, snapshot.policies...) } return cutoverNs, resolvePolicies(policies) } -func (as *activeRuleSet) matchRollups(id []byte, t time.Time) (int64, []rollupResult) { +func (as *activeRuleSet) matchRollups(id []byte, timeNs int64) (int64, []rollupResult) { // TODO(xichen): pool the rollup targets. var ( cutoverNs int64 rollups []rollupTarget ) for _, rollupRule := range as.rollupRules { - snapshot := rollupRule.ActiveSnapshot(t) + snapshot := rollupRule.ActiveSnapshot(timeNs) if snapshot == nil { continue } @@ -222,13 +226,13 @@ func (as *activeRuleSet) nextCutover(t int64) int64 { if i < len(as.cutoverTimesAsc) { return as.cutoverTimesAsc[i] } - return timeMax + return timeNsMax } // RuleSet is a set of rules associated with a namespace. type RuleSet interface { // Namespace is the metrics namespace the ruleset applies to. - Namespace() string + Namespace() []byte // Version returns the ruleset version. Version() int @@ -246,10 +250,10 @@ type RuleSet interface { type ruleSet struct { uuid string version int - namespace string + namespace []byte createdAtNs int64 lastUpdatedAtNs int64 - tombStoned bool + tombstoned bool cutoverNs int64 iterFn filters.NewSortedTagIteratorFn newIDFn NewIDFn @@ -282,10 +286,10 @@ func NewRuleSet(version int, rs *schema.RuleSet, opts Options) (RuleSet, error) return &ruleSet{ uuid: rs.Uuid, version: version, - namespace: rs.Namespace, + namespace: []byte(rs.Namespace), createdAtNs: rs.CreatedAt, lastUpdatedAtNs: rs.LastUpdatedAt, - tombStoned: rs.Tombstoned, + tombstoned: rs.Tombstoned, cutoverNs: rs.CutoverTime, iterFn: iterFn, newIDFn: opts.NewIDFn(), @@ -294,20 +298,21 @@ func NewRuleSet(version int, rs *schema.RuleSet, opts Options) (RuleSet, error) }, nil } -func (rs *ruleSet) Namespace() string { return rs.namespace } +func (rs *ruleSet) Namespace() []byte { return rs.namespace } func (rs *ruleSet) Version() int { return rs.version } func (rs *ruleSet) CutoverNs() int64 { return rs.cutoverNs } -func (rs *ruleSet) TombStoned() bool { return rs.tombStoned } +func (rs *ruleSet) TombStoned() bool { return rs.tombstoned } func (rs *ruleSet) ActiveSet(t time.Time) Matcher { + timeNs := t.UnixNano() mappingRules := make([]*mappingRule, 0, len(rs.mappingRules)) for _, mappingRule := range rs.mappingRules { - activeRule := mappingRule.ActiveRule(t) + activeRule := mappingRule.ActiveRule(timeNs) mappingRules = append(mappingRules, activeRule) } rollupRules := make([]*rollupRule, 0, len(rs.rollupRules)) for _, rollupRule := range rs.rollupRules { - activeRule := rollupRule.ActiveRule(t) + activeRule := rollupRule.ActiveRule(timeNs) rollupRules = append(rollupRules, activeRule) } return newActiveRuleSet(rs.version, rs.iterFn, rs.newIDFn, mappingRules, rollupRules) diff --git a/rules/ruleset_test.go b/rules/ruleset_test.go index ea32b6c..b0c9306 100644 --- a/rules/ruleset_test.go +++ b/rules/ruleset_test.go @@ -20,72 +20,26 @@ package rules -func b(v string) []byte { - return []byte(v) -} - -func bs(v ...string) [][]byte { - result := make([][]byte, len(v)) - for i, str := range v { - result[i] = []byte(str) - } - return result -} - -/* -func TestRollupTargetSameTransform(t *testing.T) { - policies := []policy.Policy{ - policy.NewPolicy(10*time.Second, xtime.Second, 2*24*time.Hour), - } - target := rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar2")} - inputs := []testRollupTargetData{ - { - target: rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar2"), Policies: policies}, - result: true, - }, - { - target: rollupTarget{Name: b("baz"), Tags: bs("bar1", "bar2")}, - result: false, - }, - { - target: rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar3")}, - result: false, - }, - } - for _, input := range inputs { - require.Equal(t, input.result, target.sameTransform(input.target)) - } -} - -func TestRollupTargetClone(t *testing.T) { - policies := []policy.Policy{ - policy.NewPolicy(10*time.Second, xtime.Second, 2*24*time.Hour), - } - target := rollupTarget{Name: b("foo"), Tags: bs("bar1", "bar2"), Policies: policies} - cloned := target.clone() +import ( + "bytes" + "testing" + "time" - // Cloned object should look exactly the same as the original one - require.Equal(t, target, cloned) + "github.com/m3db/m3metrics/filters" + "github.com/m3db/m3metrics/generated/proto/schema" + "github.com/m3db/m3metrics/policy" + "github.com/m3db/m3x/time" - // Change references in the cloned object should not mutate the original object - cloned.Tags[0] = b("bar3") - cloned.Policies[0] = policy.EmptyPolicy - require.Equal(t, target.Tags, bs("bar1", "bar2")) - require.Equal(t, target.Policies, policies) -} - -func TestRuleSetMatchMappingRules(t *testing.T) { - ruleSetConfig := &schema.RuleSet{ - Version: 1, - CutoverTime: time.Now().UnixNano(), - MappingRules: testMappingRulesConfig(), - } - ruleSet, err := NewRuleSet(ruleSetConfig, testRuleSetOptions()) - require.NoError(t, err) + "github.com/stretchr/testify/require" +) +func TestActiveRuleSetMatchMappingRules(t *testing.T) { inputs := []testMappingsData{ { - id: "mtagName1=mtagValue1", + id: "mtagName1=mtagValue1", + matchAt: time.Unix(0, 25000), + cutoverNs: 22000, + expireAtNs: 30000, result: []policy.Policy{ policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), @@ -93,30 +47,61 @@ func TestRuleSetMatchMappingRules(t *testing.T) { }, }, { - id: "mtagName1=mtagValue2", + id: "mtagName1=mtagValue1", + matchAt: time.Unix(0, 35000), + cutoverNs: 35000, + expireAtNs: 100000, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue2", + matchAt: time.Unix(0, 25000), + cutoverNs: 24000, + expireAtNs: 30000, result: []policy.Policy{ policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), }, }, + { + id: "mtagName1=mtagValue3", + matchAt: time.Unix(0, 25000), + cutoverNs: 0, + expireAtNs: 30000, + result: policy.DefaultVersionedPolicies(1, time.Unix(0, 25000)).Policies(), + }, } + + version := 1 + mappingRules := testMappingRules(t) + as := newActiveRuleSet( + version, + filters.NewMockSortedTagIterator, + mockNewID, + mappingRules, + nil, + ) + expectedCutovers := []int64{10000, 15000, 20000, 22000, 24000, 30000, 34000, 35000, 100000} + require.Equal(t, expectedCutovers, as.cutoverTimesAsc) for _, input := range inputs { - res := ruleSet.Match(b(input.id)) - require.Equal(t, ruleSet.Version(), res.Version()) - require.Equal(t, ruleSet.Cutover(), res.Cutover()) + res := as.Match(b(input.id), input.matchAt) + require.Equal(t, 1, res.version) + require.Equal(t, input.cutoverNs, res.cutoverNs) + require.Equal(t, input.expireAtNs, res.expireAtNs) require.Equal(t, input.result, res.Mappings().Policies()) } } -func TestRuleSetMatchRollupRules(t *testing.T) { - ruleSetConfig := &schema.RuleSet{ - RollupRules: testRollupRulesConfig(), - } - ruleSet, err := NewRuleSet(ruleSetConfig, testRuleSetOptions()) - require.NoError(t, err) - +func TestActiveRuleSetMatchRollupRules(t *testing.T) { inputs := []testRollupResultsData{ { - id: "rtagName1=rtagValue1,rtagName2=rtagValue2,rtagName3=rtagValue3", + id: "rtagName1=rtagValue1,rtagName2=rtagValue2,rtagName3=rtagValue3", + matchAt: time.Unix(0, 25000), + cutoverNs: 22000, + expireAtNs: 30000, result: []rollupResult{ { ID: b("rName1|rtagName1=rtagValue1,rtagName2=rtagValue2"), @@ -135,7 +120,10 @@ func TestRuleSetMatchRollupRules(t *testing.T) { }, }, { - id: "rtagName1=rtagValue2", + id: "rtagName1=rtagValue2", + matchAt: time.Unix(0, 25000), + cutoverNs: 24000, + expireAtNs: 30000, result: []rollupResult{ { ID: b("rName3|rtagName1=rtagValue2"), @@ -146,14 +134,30 @@ func TestRuleSetMatchRollupRules(t *testing.T) { }, }, { - id: "rtagName5=rtagValue5", - result: []rollupResult{}, + id: "rtagName5=rtagValue5", + matchAt: time.Unix(0, 25000), + cutoverNs: 0, + expireAtNs: 30000, + result: []rollupResult{}, }, } + + version := 1 + rollupRules := testRollupRules(t) + as := newActiveRuleSet( + version, + filters.NewMockSortedTagIterator, + mockNewID, + nil, + rollupRules, + ) + expectedCutovers := []int64{10000, 15000, 20000, 22000, 24000, 30000, 34000, 35000, 100000} + require.Equal(t, expectedCutovers, as.cutoverTimesAsc) for _, input := range inputs { - res := ruleSet.Match(b(input.id)) - require.Equal(t, ruleSet.Version(), res.Version()) - require.Equal(t, ruleSet.Cutover(), res.Cutover()) + res := as.Match(b(input.id), input.matchAt) + require.Equal(t, 1, res.version) + require.Equal(t, input.cutoverNs, res.cutoverNs) + require.Equal(t, input.expireAtNs, res.expireAtNs) require.Equal(t, len(input.result), res.NumRollups()) for i := 0; i < len(input.result); i++ { id, policies := res.Rollups(i) @@ -163,180 +167,715 @@ func TestRuleSetMatchRollupRules(t *testing.T) { } } -func TestTombstonedRuleSetMatch(t *testing.T) { - ruleSetConfig := &schema.RuleSet{ - Version: 1, - CutoverTime: time.Now().UnixNano(), - Tombstoned: true, +func TestRuleSetProperties(t *testing.T) { + opts := testRuleSetOptions() + version := 1 + rs := &schema.RuleSet{ + Uuid: "ruleset", + Namespace: "namespace", + CreatedAt: 1234, + LastUpdatedAt: 5678, + Tombstoned: false, + CutoverTime: 34923, + } + newRuleSet, err := NewRuleSet(version, rs, opts) + require.NoError(t, err) + ruleSet := newRuleSet.(*ruleSet) + + require.Equal(t, "ruleset", ruleSet.uuid) + require.Equal(t, []byte("namespace"), ruleSet.Namespace()) + require.Equal(t, 1, ruleSet.Version()) + require.Equal(t, int64(34923), ruleSet.CutoverNs()) + require.Equal(t, false, ruleSet.TombStoned()) +} + +func TestRuleSetActiveSet(t *testing.T) { + opts := testRuleSetOptions() + version := 1 + rs := &schema.RuleSet{ MappingRules: testMappingRulesConfig(), RollupRules: testRollupRulesConfig(), } - ruleSet, err := NewRuleSet(ruleSetConfig, testRuleSetOptions()) + newRuleSet, err := NewRuleSet(version, rs, opts) require.NoError(t, err) - expected := NewMatchResult(ruleSet.Version(), ruleSet.Cutover(), nil, nil) - id := "rtagName1=rtagValue1" - require.Equal(t, expected, ruleSet.Match(b(id))) -} + allInputs := []struct { + activeSetTime time.Time + mappingInputs []testMappingsData + rollupInputs []testRollupResultsData + }{ + { + activeSetTime: time.Unix(0, 0), + mappingInputs: []testMappingsData{ + { + id: "mtagName1=mtagValue1", + matchAt: time.Unix(0, 25000), + cutoverNs: 22000, + expireAtNs: 30000, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue1", + matchAt: time.Unix(0, 35000), + cutoverNs: 35000, + expireAtNs: 100000, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue2", + matchAt: time.Unix(0, 25000), + cutoverNs: 24000, + expireAtNs: 30000, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue3", + matchAt: time.Unix(0, 25000), + cutoverNs: 0, + expireAtNs: 30000, + result: policy.DefaultVersionedPolicies(1, time.Unix(0, 25000)).Policies(), + }, + }, + rollupInputs: []testRollupResultsData{ + { + id: "rtagName1=rtagValue1,rtagName2=rtagValue2,rtagName3=rtagValue3", + matchAt: time.Unix(0, 25000), + cutoverNs: 22000, + expireAtNs: 30000, + result: []rollupResult{ + { + ID: b("rName1|rtagName1=rtagValue1,rtagName2=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + { + ID: b("rName2|rtagName1=rtagValue1"), + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + }, + }, + { + id: "rtagName1=rtagValue2", + matchAt: time.Unix(0, 25000), + cutoverNs: 24000, + expireAtNs: 30000, + result: []rollupResult{ + { + ID: b("rName3|rtagName1=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, + }, + }, + }, + { + id: "rtagName5=rtagValue5", + matchAt: time.Unix(0, 25000), + cutoverNs: 0, + expireAtNs: 30000, + result: []rollupResult{}, + }, + }, + }, + { + activeSetTime: time.Unix(0, 30000), + mappingInputs: []testMappingsData{ + { + id: "mtagName1=mtagValue1", + matchAt: time.Unix(0, 35000), + cutoverNs: 35000, + expireAtNs: 100000, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue2", + matchAt: time.Unix(0, 35000), + cutoverNs: 24000, + expireAtNs: 100000, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue3", + matchAt: time.Unix(0, 35000), + cutoverNs: 0, + expireAtNs: 100000, + result: policy.DefaultVersionedPolicies(1, time.Unix(0, 35000)).Policies(), + }, + }, + rollupInputs: []testRollupResultsData{ + { + id: "rtagName1=rtagValue1,rtagName2=rtagValue2,rtagName3=rtagValue3", + matchAt: time.Unix(0, 35000), + cutoverNs: 35000, + expireAtNs: 100000, + result: []rollupResult{ + { + ID: b("rName1|rtagName1=rtagValue1,rtagName2=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, + }, + }, + }, + { + id: "rtagName1=rtagValue2", + matchAt: time.Unix(0, 35000), + cutoverNs: 24000, + expireAtNs: 100000, + result: []rollupResult{ + { + ID: b("rName3|rtagName1=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, + }, + }, + }, + { + id: "rtagName5=rtagValue5", + matchAt: time.Unix(0, 35000), + cutoverNs: 0, + expireAtNs: 100000, + result: []rollupResult{}, + }, + }, + }, + { + activeSetTime: time.Unix(0, 200000), + mappingInputs: []testMappingsData{ + { + id: "mtagName1=mtagValue1", + matchAt: time.Unix(0, 250000), + cutoverNs: 100000, + expireAtNs: timeNsMax, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue2", + matchAt: time.Unix(0, 250000), + cutoverNs: 24000, + expireAtNs: timeNsMax, + result: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + { + id: "mtagName1=mtagValue3", + matchAt: time.Unix(0, 250000), + cutoverNs: 0, + expireAtNs: timeNsMax, + result: policy.DefaultVersionedPolicies(1, time.Unix(0, 250000)).Policies(), + }, + }, -type testRollupTargetData struct { - target rollupTarget - result bool -} + rollupInputs: []testRollupResultsData{ + { + id: "rtagName1=rtagValue1,rtagName2=rtagValue2,rtagName3=rtagValue3", + matchAt: time.Unix(0, 250000), + cutoverNs: 100000, + expireAtNs: timeNsMax, + result: []rollupResult{ + { + ID: b("rName1|rtagName1=rtagValue1,rtagName2=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, + }, + { + ID: b("rName3|rtagName1=rtagValue1,rtagName2=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, + }, + }, + }, + { + id: "rtagName1=rtagValue2", + matchAt: time.Unix(0, 250000), + cutoverNs: 24000, + expireAtNs: timeNsMax, + result: []rollupResult{ + { + ID: b("rName3|rtagName1=rtagValue2"), + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, + }, + }, + }, + { + id: "rtagName5=rtagValue5", + matchAt: time.Unix(0, 250000), + cutoverNs: 0, + expireAtNs: timeNsMax, + result: []rollupResult{}, + }, + }, + }, + } -type testMappingsData struct { - id string - result []policy.Policy + for _, inputs := range allInputs { + as := newRuleSet.ActiveSet(inputs.activeSetTime) + for _, input := range inputs.mappingInputs { + res := as.Match(b(input.id), input.matchAt) + require.Equal(t, 1, res.version) + require.Equal(t, input.cutoverNs, res.cutoverNs) + require.Equal(t, input.expireAtNs, res.expireAtNs) + require.Equal(t, input.result, res.Mappings().Policies()) + } + for _, input := range inputs.rollupInputs { + res := as.Match(b(input.id), input.matchAt) + require.Equal(t, 1, res.version) + require.Equal(t, input.cutoverNs, res.cutoverNs) + require.Equal(t, input.expireAtNs, res.expireAtNs) + require.Equal(t, len(input.result), res.NumRollups()) + for i := 0; i < len(input.result); i++ { + id, policies := res.Rollups(i) + require.Equal(t, input.result[i].ID, id) + require.Equal(t, input.result[i].Policies, policies.Policies()) + } + } + } } -type testRollupResultsData struct { - id string - result []rollupResult -} +func testMappingRules(t *testing.T) []*mappingRule { + filter1, err := filters.NewTagsFilter( + map[string]string{"mtagName1": "mtagValue1"}, + filters.NewMockSortedTagIterator, + filters.Conjunction, + ) + require.NoError(t, err) + filter2, err := filters.NewTagsFilter( + map[string]string{"mtagName1": "mtagValue2"}, + filters.NewMockSortedTagIterator, + filters.Conjunction, + ) + require.NoError(t, err) -func testRuleSetOptions() Options { - return NewOptions(). - SetNewSortedTagIteratorFn(filters.NewMockSortedTagIterator). - SetNewIDFn(mockNewID) -} + mappingRule1 := &mappingRule{ + uuid: "mappingRule1", + snapshots: []*mappingRuleSnapshot{ + &mappingRuleSnapshot{ + name: "mappingRule1.snapshot1", + tombstoned: false, + cutoverNs: 10000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + &mappingRuleSnapshot{ + name: "mappingRule1.snapshot2", + tombstoned: false, + cutoverNs: 20000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + policy.NewPolicy(10*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + &mappingRuleSnapshot{ + name: "mappingRule1.snapshot3", + tombstoned: false, + cutoverNs: 30000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + }, + }, + }, + } -func mockNewID(name []byte, tags []TagPair) []byte { - if len(tags) == 0 { - return name + mappingRule2 := &mappingRule{ + uuid: "mappingRule2", + snapshots: []*mappingRuleSnapshot{ + &mappingRuleSnapshot{ + name: "mappingRule2.snapshot1", + tombstoned: false, + cutoverNs: 15000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + }, + }, + &mappingRuleSnapshot{ + name: "mappingRule2.snapshot2", + tombstoned: false, + cutoverNs: 22000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, + }, + &mappingRuleSnapshot{ + name: "mappingRule2.snapshot3", + tombstoned: true, + cutoverNs: 35000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, + }, + }, } - var buf bytes.Buffer - buf.Write(name) - if len(tags) > 0 { - buf.WriteString("|") - for idx, p := range tags { - buf.Write(p.Name) - buf.WriteString("=") - buf.Write(p.Value) - if idx < len(tags)-1 { - buf.WriteString(",") - } - } + + mappingRule3 := &mappingRule{ + uuid: "mappingRule3", + snapshots: []*mappingRuleSnapshot{ + &mappingRuleSnapshot{ + name: "mappingRule3.snapshot1", + tombstoned: false, + cutoverNs: 22000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, + }, + &mappingRuleSnapshot{ + name: "mappingRule3.snapshot2", + tombstoned: false, + cutoverNs: 34000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, + }, + }, } - return buf.Bytes() + + mappingRule4 := &mappingRule{ + uuid: "mappingRule4", + snapshots: []*mappingRuleSnapshot{ + &mappingRuleSnapshot{ + name: "mappingRule4.snapshot1", + tombstoned: false, + cutoverNs: 24000, + filter: filter2, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + }, + } + + mappingRule5 := &mappingRule{ + uuid: "mappingRule5", + snapshots: []*mappingRuleSnapshot{ + &mappingRuleSnapshot{ + name: "mappingRule5.snapshot1", + tombstoned: false, + cutoverNs: 100000, + filter: filter1, + policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, + }, + }, + } + + return []*mappingRule{mappingRule1, mappingRule2, mappingRule3, mappingRule4, mappingRule5} } -func testMappingRulesConfig() []*schema.MappingRule { - return []*schema.MappingRule{ - &schema.MappingRule{ - TagFilters: map[string]string{"mtagName1": "mtagValue1"}, - Policies: []*schema.Policy{ - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(10 * time.Second), - Precision: int64(time.Second), - }, - Retention: &schema.Retention{ - Period: int64(6 * time.Hour), +func testRollupRules(t *testing.T) []*rollupRule { + filter1, err := filters.NewTagsFilter( + map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + filters.NewMockSortedTagIterator, + filters.Conjunction, + ) + require.NoError(t, err) + filter2, err := filters.NewTagsFilter( + map[string]string{ + "rtagName1": "rtagValue2", + }, + filters.NewMockSortedTagIterator, + filters.Conjunction, + ) + require.NoError(t, err) + + rollupRule1 := &rollupRule{ + uuid: "rollupRule1", + snapshots: []*rollupRuleSnapshot{ + &rollupRuleSnapshot{ + name: "rollupRule1.snapshot1", + tombstoned: false, + cutoverNs: 10000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, }, }, - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(5 * time.Minute), - Precision: int64(time.Minute), - }, - Retention: &schema.Retention{ - Period: int64(48 * time.Hour), + }, + &rollupRuleSnapshot{ + name: "rollupRule1.snapshot2", + tombstoned: false, + cutoverNs: 20000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 6*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + policy.NewPolicy(10*time.Minute, xtime.Minute, 48*time.Hour), + }, }, }, - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(10 * time.Minute), - Precision: int64(time.Minute), - }, - Retention: &schema.Retention{ - Period: int64(48 * time.Hour), + }, + &rollupRuleSnapshot{ + name: "rollupRule1.snapshot3", + tombstoned: false, + cutoverNs: 30000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(30*time.Second, xtime.Second, 6*time.Hour), + }, }, }, }, }, - &schema.MappingRule{ - TagFilters: map[string]string{"mtagName1": "mtagValue1"}, - Policies: []*schema.Policy{ - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(10 * time.Second), - Precision: int64(time.Second), - }, - Retention: &schema.Retention{ - Period: int64(2 * time.Hour), + } + + rollupRule2 := &rollupRule{ + uuid: "rollupRule2", + snapshots: []*rollupRuleSnapshot{ + &rollupRuleSnapshot{ + name: "rollupRule2.snapshot1", + tombstoned: false, + cutoverNs: 15000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + }, }, }, - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(time.Minute), - Precision: int64(time.Minute), + }, + &rollupRuleSnapshot{ + name: "rollupRule2.snapshot2", + tombstoned: false, + cutoverNs: 22000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, }, - Retention: &schema.Retention{ - Period: int64(time.Hour), + }, + }, + &rollupRuleSnapshot{ + name: "rollupRule2.snapshot3", + tombstoned: false, + cutoverNs: 35000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(45*time.Second, xtime.Second, 12*time.Hour), + }, }, }, }, }, - &schema.MappingRule{ - TagFilters: map[string]string{"mtagName1": "mtagValue1"}, - Policies: []*schema.Policy{ - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(10 * time.Second), - Precision: int64(time.Second), + } + + rollupRule3 := &rollupRule{ + uuid: "rollupRule3", + snapshots: []*rollupRuleSnapshot{ + &rollupRuleSnapshot{ + name: "rollupRule3.snapshot1", + tombstoned: false, + cutoverNs: 22000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 12*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, 24*time.Hour), + policy.NewPolicy(5*time.Minute, xtime.Minute, 48*time.Hour), + }, }, - Retention: &schema.Retention{ - Period: int64(12 * time.Hour), + { + Name: b("rName2"), + Tags: [][]byte{b("rtagName1")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 24*time.Hour), + }, }, }, - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(time.Minute), - Precision: int64(time.Minute), - }, - Retention: &schema.Retention{ - Period: int64(24 * time.Hour), + }, + &rollupRuleSnapshot{ + name: "rollupRule3.snapshot2", + tombstoned: false, + cutoverNs: 34000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName1"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(10*time.Second, xtime.Second, 2*time.Hour), + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, }, }, - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(5 * time.Minute), - Precision: int64(time.Minute), - }, - Retention: &schema.Retention{ - Period: int64(48 * time.Hour), + }, + }, + } + + rollupRule4 := &rollupRule{ + uuid: "rollupRule4", + snapshots: []*rollupRuleSnapshot{ + &rollupRuleSnapshot{ + name: "rollupRule4.snapshot1", + tombstoned: false, + cutoverNs: 24000, + filter: filter2, + targets: []rollupTarget{ + { + Name: b("rName3"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, }, }, }, }, - &schema.MappingRule{ - TagFilters: map[string]string{"mtagName1": "mtagValue2"}, - Policies: []*schema.Policy{ - &schema.Policy{ - Resolution: &schema.Resolution{ - WindowSize: int64(10 * time.Second), - Precision: int64(time.Second), - }, - Retention: &schema.Retention{ - Period: int64(24 * time.Hour), + } + + rollupRule5 := &rollupRule{ + uuid: "rollupRule5", + snapshots: []*rollupRuleSnapshot{ + &rollupRuleSnapshot{ + name: "rollupRule5.snapshot1", + tombstoned: false, + cutoverNs: 100000, + filter: filter1, + targets: []rollupTarget{ + { + Name: b("rName3"), + Tags: [][]byte{b("rtagName1"), b("rtagName2")}, + Policies: []policy.Policy{ + policy.NewPolicy(time.Minute, xtime.Minute, time.Hour), + }, }, }, }, }, } + + return []*rollupRule{rollupRule1, rollupRule2, rollupRule3, rollupRule4, rollupRule5} } -func testRollupRulesConfig() []*schema.RollupRule { - return []*schema.RollupRule{ - { - TagFilters: map[string]string{ - "rtagName1": "rtagValue1", - "rtagName2": "rtagValue2", - }, - Targets: []*schema.RollupTarget{ - &schema.RollupTarget{ - Name: "rName1", - Tags: []string{"rtagName1", "rtagName2"}, +func testRuleSetOptions() Options { + return NewOptions(). + SetNewSortedTagIteratorFn(filters.NewMockSortedTagIterator). + SetNewIDFn(mockNewID) +} + +func mockNewID(name []byte, tags []TagPair) []byte { + if len(tags) == 0 { + return name + } + var buf bytes.Buffer + buf.Write(name) + if len(tags) > 0 { + buf.WriteString("|") + for idx, p := range tags { + buf.Write(p.Name) + buf.WriteString("=") + buf.Write(p.Value) + if idx < len(tags)-1 { + buf.WriteString(",") + } + } + } + return buf.Bytes() +} + +func testMappingRulesConfig() []*schema.MappingRule { + return []*schema.MappingRule{ + &schema.MappingRule{ + Uuid: "mappingRule1", + Snapshots: []*schema.MappingRuleSnapshot{ + &schema.MappingRuleSnapshot{ + Name: "mappingRule1.snapshot1", + Tombstoned: false, + CutoverTime: 10000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + }, + }, + &schema.MappingRuleSnapshot{ + Name: "mappingRule1.snapshot2", + Tombstoned: false, + CutoverTime: 20000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, Policies: []*schema.Policy{ &schema.Policy{ Resolution: &schema.Resolution{ @@ -367,17 +906,50 @@ func testRollupRulesConfig() []*schema.RollupRule { }, }, }, + &schema.MappingRuleSnapshot{ + Name: "mappingRule1.snapshot3", + Tombstoned: false, + CutoverTime: 30000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(30 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(6 * time.Hour), + }, + }, + }, + }, }, }, - { - TagFilters: map[string]string{ - "rtagName1": "rtagValue1", - "rtagName2": "rtagValue2", - }, - Targets: []*schema.RollupTarget{ - &schema.RollupTarget{ - Name: "rName1", - Tags: []string{"rtagName1", "rtagName2"}, + &schema.MappingRule{ + Uuid: "mappingRule2", + Snapshots: []*schema.MappingRuleSnapshot{ + &schema.MappingRuleSnapshot{ + Name: "mappingRule2.snapshot1", + Tombstoned: false, + CutoverTime: 15000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(12 * time.Hour), + }, + }, + }, + }, + &schema.MappingRuleSnapshot{ + Name: "mappingRule2.snapshot2", + Tombstoned: false, + CutoverTime: 22000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, Policies: []*schema.Policy{ &schema.Policy{ Resolution: &schema.Resolution{ @@ -399,17 +971,33 @@ func testRollupRulesConfig() []*schema.RollupRule { }, }, }, + &schema.MappingRuleSnapshot{ + Name: "mappingRule2.snapshot3", + Tombstoned: true, + CutoverTime: 35000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(45 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(12 * time.Hour), + }, + }, + }, + }, }, }, - { - TagFilters: map[string]string{ - "rtagName1": "rtagValue1", - "rtagName2": "rtagValue2", - }, - Targets: []*schema.RollupTarget{ - &schema.RollupTarget{ - Name: "rName1", - Tags: []string{"rtagName1", "rtagName2"}, + &schema.MappingRule{ + Uuid: "mappingRule3", + Snapshots: []*schema.MappingRuleSnapshot{ + &schema.MappingRuleSnapshot{ + Name: "mappingRule3.snapshot1", + Tombstoned: false, + CutoverTime: 22000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, Policies: []*schema.Policy{ &schema.Policy{ Resolution: &schema.Resolution{ @@ -440,9 +1028,11 @@ func testRollupRulesConfig() []*schema.RollupRule { }, }, }, - &schema.RollupTarget{ - Name: "rName2", - Tags: []string{"rtagName1"}, + &schema.MappingRuleSnapshot{ + Name: "mappingRule3.snapshot2", + Tombstoned: false, + CutoverTime: 34000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, Policies: []*schema.Policy{ &schema.Policy{ Resolution: &schema.Resolution{ @@ -450,29 +1040,418 @@ func testRollupRulesConfig() []*schema.RollupRule { Precision: int64(time.Second), }, Retention: &schema.Retention{ - Period: int64(24 * time.Hour), + Period: int64(2 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(time.Hour), }, }, }, }, }, }, - { - TagFilters: map[string]string{ - "rtagName1": "rtagValue2", + &schema.MappingRule{ + Uuid: "mappingRule4", + Snapshots: []*schema.MappingRuleSnapshot{ + &schema.MappingRuleSnapshot{ + Name: "mappingRule4.snapshot1", + Tombstoned: false, + CutoverTime: 24000, + TagFilters: map[string]string{"mtagName1": "mtagValue2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + }, + }, }, - Targets: []*schema.RollupTarget{ - &schema.RollupTarget{ - Name: "rName3", - Tags: []string{"rtagName1", "rtagName2"}, + }, + &schema.MappingRule{ + Uuid: "mappingRule5", + Snapshots: []*schema.MappingRuleSnapshot{ + &schema.MappingRuleSnapshot{ + Name: "mappingRule5.snapshot1", + Tombstoned: false, + CutoverTime: 100000, + TagFilters: map[string]string{"mtagName1": "mtagValue1"}, Policies: []*schema.Policy{ &schema.Policy{ Resolution: &schema.Resolution{ - WindowSize: int64(time.Minute), - Precision: int64(time.Minute), + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), }, Retention: &schema.Retention{ - Period: int64(time.Hour), + Period: int64(24 * time.Hour), + }, + }, + }, + }, + }, + }, + } +} + +func testRollupRulesConfig() []*schema.RollupRule { + return []*schema.RollupRule{ + &schema.RollupRule{ + Uuid: "rollupRule1", + Snapshots: []*schema.RollupRuleSnapshot{ + &schema.RollupRuleSnapshot{ + Name: "rollupRule1.snapshot1", + Tombstoned: false, + CutoverTime: 10000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + }, + }, + }, + }, + &schema.RollupRuleSnapshot{ + Name: "rollupRule1.snapshot2", + Tombstoned: false, + CutoverTime: 20000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(6 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(5 * time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(48 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(48 * time.Hour), + }, + }, + }, + }, + }, + }, + &schema.RollupRuleSnapshot{ + Name: "rollupRule1.snapshot3", + Tombstoned: false, + CutoverTime: 30000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(30 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(6 * time.Hour), + }, + }, + }, + }, + }, + }, + }, + }, + &schema.RollupRule{ + Uuid: "rollupRule2", + Snapshots: []*schema.RollupRuleSnapshot{ + &schema.RollupRuleSnapshot{ + Name: "rollupRule2.snapshot1", + Tombstoned: false, + CutoverTime: 15000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(12 * time.Hour), + }, + }, + }, + }, + }, + }, + &schema.RollupRuleSnapshot{ + Name: "rollupRule2.snapshot2", + Tombstoned: false, + CutoverTime: 22000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(2 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(time.Hour), + }, + }, + }, + }, + }, + }, + &schema.RollupRuleSnapshot{ + Name: "rollupRule2.snapshot3", + Tombstoned: true, + CutoverTime: 35000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(45 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(12 * time.Hour), + }, + }, + }, + }, + }, + }, + }, + }, + &schema.RollupRule{ + Uuid: "rollupRule3", + Snapshots: []*schema.RollupRuleSnapshot{ + &schema.RollupRuleSnapshot{ + Name: "rollupRule3.snapshot1", + Tombstoned: false, + CutoverTime: 22000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(12 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(5 * time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(48 * time.Hour), + }, + }, + }, + }, + &schema.RollupTarget{ + Name: "rName2", + Tags: []string{"rtagName1"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(24 * time.Hour), + }, + }, + }, + }, + }, + }, + &schema.RollupRuleSnapshot{ + Name: "rollupRule3.snapshot2", + Tombstoned: false, + CutoverTime: 34000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName1", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(10 * time.Second), + Precision: int64(time.Second), + }, + Retention: &schema.Retention{ + Period: int64(2 * time.Hour), + }, + }, + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(time.Hour), + }, + }, + }, + }, + }, + }, + }, + }, + &schema.RollupRule{ + Uuid: "rollupRule4", + Snapshots: []*schema.RollupRuleSnapshot{ + &schema.RollupRuleSnapshot{ + Name: "rollupRule4.snapshot1", + Tombstoned: false, + CutoverTime: 24000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName3", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(time.Hour), + }, + }, + }, + }, + }, + }, + }, + }, + &schema.RollupRule{ + Uuid: "rollupRule5", + Snapshots: []*schema.RollupRuleSnapshot{ + &schema.RollupRuleSnapshot{ + Name: "rollupRule5.snapshot1", + Tombstoned: false, + CutoverTime: 100000, + TagFilters: map[string]string{ + "rtagName1": "rtagValue1", + "rtagName2": "rtagValue2", + }, + Targets: []*schema.RollupTarget{ + &schema.RollupTarget{ + Name: "rName3", + Tags: []string{"rtagName1", "rtagName2"}, + Policies: []*schema.Policy{ + &schema.Policy{ + Resolution: &schema.Resolution{ + WindowSize: int64(time.Minute), + Precision: int64(time.Minute), + }, + Retention: &schema.Retention{ + Period: int64(time.Hour), + }, + }, }, }, }, @@ -481,4 +1460,31 @@ func testRollupRulesConfig() []*schema.RollupRule { }, } } -*/ + +type testMappingsData struct { + id string + matchAt time.Time + cutoverNs int64 + expireAtNs int64 + result []policy.Policy +} + +type testRollupResultsData struct { + id string + matchAt time.Time + cutoverNs int64 + expireAtNs int64 + result []rollupResult +} + +func b(v string) []byte { + return []byte(v) +} + +func bs(v ...string) [][]byte { + result := make([][]byte, len(v)) + for i, str := range v { + result[i] = []byte(str) + } + return result +}