-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
models.go
220 lines (191 loc) · 6.26 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
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2016 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lib
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"strconv"
"strings"
"sync"
"time"
"github.com/loadimpact/k6/lib/types"
"github.com/pkg/errors"
"gopkg.in/guregu/null.v3"
)
// Separator for group IDs.
const GroupSeparator = "::"
// Error 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 '::'")
// 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
// 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
}
func (s Stage) MarshalJSON() ([]byte, error) {
return json.Marshal(StageFields(s))
}
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"`
Checks map[string]*Check `json:"checks"`
groupMutex sync.Mutex
checkMutex sync.Mutex
}
// 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) {
if strings.Contains(name, GroupSeparator) {
return nil, ErrNameContainsGroupSeparator
}
path := name
if parent != nil {
path = parent.Path + GroupSeparator + path
}
hash := md5.Sum([]byte(path))
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 {
group, err := NewGroup(name, g)
if err != nil {
return nil, err
}
g.Groups[name] = group
return group, nil
}
return group, 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 {
check, err := NewCheck(name, g)
if err != nil {
return nil, err
}
g.Checks[name] = check
return check, nil
}
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"`
}
// 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))
id := hex.EncodeToString(hash[:])
return &Check{
ID: id,
Path: path,
Group: group,
Name: name,
}, nil
}