forked from grafana/k6
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodels.go
226 lines (195 loc) · 6.49 KB
/
models.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package lib
import (
"crypto/md5" //nolint:gosec
"encoding/hex"
"encoding/json"
"errors"
"strconv"
"strings"
"sync"
"time"
"gopkg.in/guregu/null.v3"
"go.k6.io/k6/lib/types"
)
// GroupSeparator for group IDs.
const GroupSeparator = "::"
// RootGroupPath is the id of the root group
//
// Note(@mstoykov): the constant shouldn't be used in all tests in order to not couple the tests too much with it.
// Changing this will be a breaking change and in this way it will be more obvious.
const RootGroupPath = ""
// ErrNameContainsGroupSeparator is emitted if you attempt to instantiate a Group or Check that contains the separator.
var ErrNameContainsGroupSeparator = errors.New("group and check names may not contain '" + GroupSeparator + "'")
// StageFields defines the fields used for a Stage; this is a dumb hack to make the JSON code
// cleaner. pls fix.
type StageFields struct {
// Duration of the stage.
Duration types.NullDuration `json:"duration"`
// If Valid, the VU count will be linearly interpolated towards this value.
Target null.Int `json:"target"`
}
// A Stage defines a step in a test's timeline.
type Stage StageFields
// UnmarshalJSON implements the json.Unmarshaler interface
// for some reason, implementing UnmarshalText makes encoding/json treat the type as a string.
func (s *Stage) UnmarshalJSON(b []byte) error {
var fields StageFields
if err := json.Unmarshal(b, &fields); err != nil {
return err
}
*s = Stage(fields)
return nil
}
// MarshalJSON implements the json.Marshaler interface
func (s Stage) MarshalJSON() ([]byte, error) {
return json.Marshal(StageFields(s))
}
// UnmarshalText implements the encoding.TextUnmarshaler interface
func (s *Stage) UnmarshalText(b []byte) error {
var stage Stage
parts := strings.SplitN(string(b), ":", 2)
if len(parts) > 0 && parts[0] != "" {
d, err := time.ParseDuration(parts[0])
if err != nil {
return err
}
stage.Duration = types.NullDurationFrom(d)
}
if len(parts) > 1 && parts[1] != "" {
t, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return err
}
stage.Target = null.IntFrom(t)
}
*s = stage
return nil
}
// A Group is an organisational block, that samples and checks may be tagged with.
//
// For more information, refer to the js/modules/k6.K6.Group() function.
type Group struct {
// Arbitrary name of the group.
Name string `json:"name"`
// A group may belong to another group, which may belong to another group, etc. The Path
// describes the hierarchy leading down to this group, with the segments delimited by '::'.
// As an example: a group "Inner" inside a group named "Outer" would have a path of
// "::Outer::Inner". The empty first item is the root group, which is always named "".
Parent *Group `json:"-"`
Path string `json:"path"`
// A group's ID is a hash of the Path. It is deterministic between different k6
// instances of the same version, but should be treated as opaque - the hash function
// or length may change.
ID string `json:"id"`
// Groups and checks that are children of this group.
Groups map[string]*Group `json:"groups"`
OrderedGroups []*Group `json:"-"`
Checks map[string]*Check `json:"checks"`
OrderedChecks []*Check `json:"-"`
groupMutex sync.Mutex
checkMutex sync.Mutex
}
// NewGroup creates a new group with the given name and parent group.
//
// The root group must be created with the name "" and parent set to nil; this is the only case
// where a nil parent or empty name is allowed.
func NewGroup(name string, parent *Group) (*Group, error) {
old := RootGroupPath
if parent != nil {
old = parent.Path
}
path, err := NewGroupPath(old, name)
if err != nil {
return nil, err
}
hash := md5.Sum([]byte(path)) //nolint:gosec
id := hex.EncodeToString(hash[:])
return &Group{
ID: id,
Path: path,
Name: name,
Parent: parent,
Groups: make(map[string]*Group),
Checks: make(map[string]*Check),
}, nil
}
// Group creates a child group belonging to this group.
// This is safe to call from multiple goroutines simultaneously.
func (g *Group) Group(name string) (*Group, error) {
g.groupMutex.Lock()
defer g.groupMutex.Unlock()
group, ok := g.Groups[name]
if !ok {
var err error
group, err = NewGroup(name, g)
if err != nil {
return nil, err
}
g.Groups[name] = group
g.OrderedGroups = append(g.OrderedGroups, group)
}
return group, nil
}
// NewGroupPath ...
func NewGroupPath(old, path string) (string, error) {
if strings.Contains(path, GroupSeparator) {
return "", ErrNameContainsGroupSeparator
}
if old == RootGroupPath && path == RootGroupPath {
return RootGroupPath, nil
}
return old + GroupSeparator + path, nil
}
// Check creates a child check belonging to this group.
// This is safe to call from multiple goroutines simultaneously.
func (g *Group) Check(name string) (*Check, error) {
g.checkMutex.Lock()
defer g.checkMutex.Unlock()
check, ok := g.Checks[name]
if !ok {
var err error
check, err = NewCheck(name, g)
if err != nil {
return nil, err
}
g.Checks[name] = check
g.OrderedChecks = append(g.OrderedChecks, check)
}
return check, nil
}
// A Check stores a series of successful or failing tests against a value.
//
// For more information, refer to the js/modules/k6.K6.Check() function.
type Check struct {
// Arbitrary name of the check.
Name string `json:"name"`
// A Check belongs to a Group, which may belong to other groups. The Path describes
// the hierarchy of these groups, with the segments delimited by '::'.
// As an example: a check "My Check" within a group "Inner" within a group "Outer"
// would have a Path of "::Outer::Inner::My Check". The empty first item is the root group,
// which is always named "".
Group *Group `json:"-"`
Path string `json:"path"`
// A check's ID is a hash of the Path. It is deterministic between different k6
// instances of the same version, but should be treated as opaque - the hash function
// or length may change.
ID string `json:"id"`
// Counters for how many times this check has passed and failed respectively.
Passes int64 `json:"passes"`
Fails int64 `json:"fails"`
}
// NewCheck creates a new check with the given name and parent group. The group may not be nil.
func NewCheck(name string, group *Group) (*Check, error) {
if strings.Contains(name, GroupSeparator) {
return nil, ErrNameContainsGroupSeparator
}
path := group.Path + GroupSeparator + name
hash := md5.Sum([]byte(path)) //nolint:gosec
id := hex.EncodeToString(hash[:])
return &Check{
ID: id,
Path: path,
Group: group,
Name: name,
}, nil
}