tsd_metrics 0.2.0 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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