tsd_metrics 0.2.0 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "definitions": {
4
+ "sampleObject" : {
5
+ "type": "object",
6
+ "properties" : {
7
+ "unit": {
8
+ "type": "string",
9
+ "enum": ["nanosecond","microsecond", "millisecond", "second", "minute", "hour", "day", "week", "bit", "byte", "kilobit", "kilobyte", "megabit", "megabyte", "gigabit", "gigabyte", "terabyte", "petabyte"]
10
+ },
11
+ "value": {
12
+ "type": "number"
13
+ }
14
+ },
15
+ "required": ["value"]
16
+ },
17
+ "dataElement": {
18
+ "type": "object",
19
+ "properties": {
20
+ "values": {
21
+ "type": "array",
22
+ "items": {
23
+ "$ref": "#/definitions/sampleObject"
24
+ }
25
+ }
26
+ },
27
+ "required": ["values"]
28
+ },
29
+ "metricsList": {
30
+ "type": "object",
31
+ "additionalProperties": {
32
+ "$ref": "#/definitions/dataElement"
33
+ }
34
+ },
35
+ "data": {
36
+ "type": "object",
37
+ "properties":{
38
+ "annotations": {
39
+ "type":"object",
40
+ "properties": {
41
+ "finalTimestamp": {
42
+ "type":"string",
43
+ "format": "date-time"
44
+ },
45
+ "initTimestamp": {
46
+ "type":"string",
47
+ "format": "date-time"
48
+ }
49
+ },
50
+ "required": ["initTimestamp", "finalTimestamp"],
51
+ "additionalProperties": {
52
+ "type": "string"
53
+ }
54
+ },
55
+ "counters": {
56
+ "$ref": "#/definitions/metricsList"
57
+ },
58
+ "gauges": {
59
+ "$ref": "#/definitions/metricsList"
60
+ },
61
+ "timers": {
62
+ "$ref": "#/definitions/metricsList"
63
+ },
64
+ "version": {
65
+ "type":"string",
66
+ "pattern": "^2e$"
67
+ }
68
+ },
69
+ "required": ["annotations", "version"]
70
+ }
71
+ },
72
+
73
+ "title": "Query Log",
74
+ "description": "log file entry for ingestion by tsd aggregator",
75
+ "type":"object",
76
+
77
+ "properties":{
78
+ "time": {
79
+ "type":"string",
80
+ "format": "date-time"
81
+ },
82
+ "name": {
83
+ "type":"string",
84
+ "pattern": "^aint.metrics$"
85
+ },
86
+ "level": {
87
+ "type":"string",
88
+ "pattern": "^info$"
89
+ },
90
+ "data": {
91
+ "$ref": "#/definitions/data"
92
+ },
93
+ "id": {
94
+ "type":"string"
95
+ },
96
+ "context": {
97
+ "type":"object",
98
+ "properties": {
99
+ }
100
+ }
101
+ },
102
+ "required": ["time", "name", "level", "data"]
103
+ }
@@ -0,0 +1,40 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module TsdMetrics
16
+ # Not threadsafe
17
+ class AsyncQueueWriter
18
+ def initialize(queue, logger)
19
+ @queue = queue
20
+ @logger = logger
21
+ end
22
+
23
+ def start
24
+ Thread.new do
25
+ while true
26
+ tryPopQueueToFile
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def tryPopQueueToFile
34
+ line = @queue.pop
35
+ return if line == nil
36
+ @logger << line
37
+ @logger << "\n"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'counter_sample'
16
+
17
+ module TsdMetrics
18
+ class Counter
19
+
20
+ def initialize(parentMetric)
21
+ @parentMetric = parentMetric
22
+ @samples = []
23
+ @staticSample = nil
24
+ end
25
+
26
+ def createNewSample
27
+ newSample = CounterSample.new(@parentMetric)
28
+ @samples.push(newSample)
29
+ return newSample
30
+ end
31
+
32
+ def values
33
+ @samples.map do |s|
34
+ s.sampleRepresentation
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def getFirstOrNewStaticSample
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module TsdMetrics
16
+ class CounterSample
17
+ attr_reader :value
18
+ def initialize(metricStatusSupplier)
19
+ @metricStatusSupplier = metricStatusSupplier
20
+ @value = 0
21
+ end
22
+ def increment(magnitude = 1)
23
+ if @metricStatusSupplier.metricIsClosed
24
+ TsdMetrics.errorLogger.warn("Increment or decrement called on Counter after metric has been closed")
25
+ return
26
+ end
27
+ @value += magnitude
28
+ end
29
+ def decrement(magnitude = 1)
30
+ increment(-1*magnitude)
31
+ end
32
+ def sampleRepresentation
33
+ # Always unitless
34
+ {value: @value}
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module TsdMetrics
16
+ class Error < RuntimeError
17
+ end
18
+ class MetricClosedError < Error
19
+ end
20
+ class OutputThreadError < Error
21
+ end
22
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'json'
16
+ require 'time'
17
+
18
+ module TsdMetrics
19
+ class JsonFormattingSink # Implements metricSink
20
+ def initialize(outputStream)
21
+ @outputStream = outputStream
22
+ end
23
+ def receive(tsdMetricEvent)
24
+ hash = {
25
+ time: Time.now.utc.iso8601(3),
26
+ name: "aint.metrics",
27
+ level: "info",
28
+ data: {
29
+ version: "2e",
30
+ gauges: proxyValuesProperty(tsdMetricEvent.gauges),
31
+ timers: proxyValuesProperty(tsdMetricEvent.timers),
32
+ counters: proxyValuesProperty(tsdMetricEvent.counters),
33
+ annotations: tsdMetricEvent.annotations
34
+ }
35
+ }
36
+ haveMetrics = [:gauges, :timers, :counters].any? do |metricType|
37
+ hash[:data][metricType].length > 0
38
+ end
39
+ # The timestamp annotations are always present, but we're looking for
40
+ # any further annotations.
41
+ haveMetrics = true if hash[:data][:annotations].length > 2
42
+
43
+ return unless haveMetrics
44
+
45
+ hash[:data][:annotations][:initTimestamp] = hash[:data][:annotations][:initTimestamp].utc.iso8601(3)
46
+ hash[:data][:annotations][:finalTimestamp] = hash[:data][:annotations][:finalTimestamp].utc.iso8601(3)
47
+ @outputStream.write(hash.to_json)
48
+ end
49
+
50
+ def record(tsdMetricEvent)
51
+ receive(tsdMetricEvent)
52
+ end
53
+
54
+ private
55
+
56
+ def proxyValuesProperty(hash)
57
+ hash.merge(hash) do |key, val, _|
58
+ {values: val}
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module TsdMetrics
16
+ class MetricBuilderForSingleStructReceiver
17
+ def initialize(structReceiver)
18
+ @structReceiver = structReceiver
19
+ end
20
+ def build
21
+ TsdMetric.new(Time.now, @structReceiver, Mutex.new)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module TsdMetrics
16
+ class QueueWriter
17
+ def initialize(queue)
18
+ @queue = queue
19
+ end
20
+ def write(s)
21
+ @queue << s
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'timer_sample'
16
+
17
+ module TsdMetrics
18
+ class Timer
19
+ def initialize(parentMetric)
20
+ @parentMetric = parentMetric
21
+ @samples = []
22
+ end
23
+
24
+ def createNewSample
25
+ sample = TimerSample.new(@parentMetric)
26
+ @samples.push sample
27
+ sample
28
+ end
29
+
30
+ def addDuration(duration)
31
+ sample = TimerSample.new(@parentMetric)
32
+ sample.duration = duration
33
+ @samples.push sample
34
+ end
35
+
36
+ def samples
37
+ durations = []
38
+ @samples.each do |s|
39
+ if s.stopped?
40
+ durations.push s.sampleRepresentation
41
+ else
42
+ TsdMetrics.errorLogger.warn("Unstopped timer dropped from log")
43
+ end
44
+ end
45
+ durations
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,69 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module TsdMetrics
16
+ class TimerSample
17
+ attr_reader :duration, :unit
18
+
19
+ def initialize(metricStatusSupplier)
20
+ @metricStatusSupplier = metricStatusSupplier
21
+ @startTime = Time.now
22
+ @duration = nil
23
+ @unit = :nanosecond
24
+ end
25
+
26
+ def stop
27
+ if @startTime == nil
28
+ TsdMetrics.errorLogger.warn("Stop called on already-stopped Timer sample")
29
+ return
30
+ end
31
+ if @metricStatusSupplier.metricIsClosed
32
+ TsdMetrics.errorLogger.warn("Stop called on Timer after metric has been closed")
33
+ return
34
+ end
35
+ now = Time.now
36
+ diffSecs = now.tv_sec - @startTime.tv_sec
37
+ diffNanoSecs = now.tv_nsec - @startTime.tv_nsec
38
+ diff = ((10**9) * diffSecs) + diffNanoSecs
39
+ @duration = diff
40
+ @startTime = nil
41
+ end
42
+
43
+ # Deprecated: Instead use the more ruby-esque #running?
44
+ def isRunning
45
+ return running?
46
+ end
47
+
48
+ def running?
49
+ return @startTime != nil
50
+ end
51
+
52
+ def stopped?
53
+ return ! running?
54
+ end
55
+
56
+ def set(duration, unit)
57
+ @duration = duration
58
+ @unit = unit
59
+ end
60
+
61
+ def sampleRepresentation
62
+ if @unit == :noUnit
63
+ {value: @duration}
64
+ else
65
+ {value: @duration, unit: @unit}
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,200 @@
1
+ # Copyright 2014 Groupon.com
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'timer_sample'
16
+ require_relative 'timer'
17
+ require_relative 'counter'
18
+ require_relative 'exceptions'
19
+ require_relative 'units'
20
+
21
+ module TsdMetrics
22
+ class TsdMetric
23
+ # Implements TsdMetricEvent interface
24
+ attr_reader :annotations, :gauges
25
+
26
+ def initialize(startTime, metricSink, mutexStrategy)
27
+ @metricSink = metricSink
28
+ @annotations = {initTimestamp: startTime}
29
+ @mutexStrategy = mutexStrategy
30
+ @gauges = {}
31
+ @metrics = {timers: {}, counters: {}}
32
+ @metricClasses = {timers: Timer, counters: Counter}
33
+ @staticSamples = {timers: {}, counters: {}}
34
+ @closed = false
35
+ end
36
+
37
+ def open?
38
+ @mutexStrategy.synchronize do
39
+ not @closed
40
+ end
41
+ end
42
+
43
+ def setGauge(name, value, unit = :noUnit)
44
+ @mutexStrategy.synchronize do
45
+ assertNotClosed
46
+ assertValidUnit(unit)
47
+ @gauges[name] ||= []
48
+ @gauges[name].push({value: value, unit: unit}.select{|k, v| v != :noUnit})
49
+ end
50
+ end
51
+
52
+ def startTimer(name)
53
+ @mutexStrategy.synchronize do
54
+ assertNotClosed
55
+ # Timer sample is started on creation
56
+ pushNewStaticSample(:timers, name)
57
+ end
58
+ end
59
+
60
+ def stopTimer(name)
61
+ @mutexStrategy.synchronize do
62
+ assertNotClosed
63
+ sample = getStaticSample(:timers, name)
64
+ sample.stop
65
+ end
66
+ end
67
+
68
+ def setTimer(name, duration, unit = :noUnit)
69
+ @mutexStrategy.synchronize do
70
+ assertNotClosed
71
+ assertValidUnit(unit)
72
+ pushNewStaticSample(:timers, name)
73
+ sample = getStaticSample(:timers, name)
74
+ sample.stop()
75
+ sample.set(duration, unit)
76
+ end
77
+ end
78
+
79
+ def createTimer(name)
80
+ @mutexStrategy.synchronize do
81
+ assertNotClosed
82
+ ensureMetricExists(:timers, name)
83
+ sample = getMetric(:timers, name).createNewSample
84
+ sample
85
+ end
86
+ end
87
+
88
+ def resetCounter(name)
89
+ @mutexStrategy.synchronize do
90
+ assertNotClosed
91
+ ensureCounterExists(name)
92
+ @staticSamples[:counters][name] = getMetric(:counters, name).createNewSample
93
+ end
94
+ end
95
+
96
+ def incrementCounter(name, magnitude=1)
97
+ @mutexStrategy.synchronize do
98
+ assertNotClosed
99
+ ensureStaticCounterSampleExists(name)
100
+ getStaticSample(:counters, name).increment(magnitude)
101
+ end
102
+ end
103
+
104
+ def decrementCounter(name, magnitude=1)
105
+ @mutexStrategy.synchronize do
106
+ assertNotClosed
107
+ incrementCounter(name, -1*magnitude)
108
+ end
109
+ end
110
+
111
+ def createCounter(name)
112
+ @mutexStrategy.synchronize do
113
+ assertNotClosed
114
+ ensureMetricExists(:counters, name)
115
+ getMetric(:counters, name).createNewSample
116
+ end
117
+ end
118
+
119
+ def annotate(name, value)
120
+ @mutexStrategy.synchronize do
121
+ assertNotClosed
122
+ @annotations[name] = value
123
+ end
124
+ end
125
+
126
+ def close
127
+ @mutexStrategy.synchronize do
128
+ assertNotClosed
129
+ @closed = true
130
+ @annotations[:finalTimestamp] = Time.now()
131
+ @metricSink.record(self)
132
+ end
133
+ end
134
+
135
+ def timers
136
+ samplesHash = {}
137
+ getMetricsOfType(:timers).each do |timerName,timer|
138
+ samplesHash[timerName] = timer.samples
139
+ end
140
+ samplesHash
141
+ end
142
+
143
+ def counters
144
+ countersHash = {}
145
+ getMetricsOfType(:counters).each do |k,v|
146
+ countersHash[k] = v.values
147
+ end
148
+ countersHash
149
+ end
150
+
151
+ # "Implements" metricStatusSupplier
152
+ def metricIsClosed
153
+ return @closed
154
+ end
155
+
156
+ private
157
+
158
+ def ensureCounterExists(name)
159
+ ensureMetricExists(:counters, name)
160
+ end
161
+
162
+ def ensureMetricExists(metricType, name)
163
+ @metrics[metricType][name] ||= @metricClasses[metricType].new(self)
164
+ end
165
+
166
+ def ensureStaticSampleExists(sampleType, name)
167
+ ensureMetricExists(sampleType, name)
168
+ @staticSamples[sampleType][name] ||= getMetric(sampleType, name).createNewSample
169
+ end
170
+
171
+ def ensureStaticCounterSampleExists(name)
172
+ ensureStaticSampleExists(:counters, name)
173
+ end
174
+
175
+ def pushNewStaticSample(metricType, name)
176
+ ensureMetricExists(metricType, name)
177
+ @staticSamples[metricType][name] = getMetric(metricType, name).createNewSample
178
+ end
179
+
180
+ def getMetric(metricType, name)
181
+ @metrics[metricType][name]
182
+ end
183
+
184
+ def getMetricsOfType(metricType)
185
+ @metrics[metricType]
186
+ end
187
+
188
+ def getStaticSample(sampleType, name)
189
+ @staticSamples[sampleType][name]
190
+ end
191
+
192
+ def assertNotClosed
193
+ raise MetricClosedError if @closed
194
+ end
195
+
196
+ def assertValidUnit(unit)
197
+ raise MetricClosedError unless UnitsUtils.isValidUnitValue?(unit)
198
+ end
199
+ end
200
+ end