Skip to content

Commit 2c2a4e2

Browse files
committed
feat(topology): add generic topology support
1 parent 2de5967 commit 2c2a4e2

18 files changed

+634
-22
lines changed

e2e/reportApmMetrics.spec.js

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ test('should report apm metrics', function (t) {
2323
function (uri, requestBody) {
2424
t.pass('collector sent rpm metrics')
2525
})
26+
serviceMocks.mockEdgeMetricsRequest(
27+
TRACE_COLLECTOR_API_URL,
28+
TRACE_API_KEY_TEST,
29+
TRACE_SERVICE_KEY_TEST,
30+
1,
31+
function (uri, requestBody) {
32+
t.pass('collector sent edge metrics')
33+
})
2634
serviceMocks.mockApmMetricsRequest(
2735
TRACE_COLLECTOR_API_URL,
2836
TRACE_API_KEY_TEST,

e2e/reportHttpRequest.spec.js

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ test('should report http requests', function (t) {
2727
TRACE_API_KEY_TEST,
2828
42,
2929
Number.MAX_SAFE_INTEGER) // set the times parameter high so the http mock catches all
30+
serviceMocks.mockEdgeMetricsRequest(
31+
TRACE_COLLECTOR_API_URL,
32+
TRACE_API_KEY_TEST,
33+
42,
34+
Number.MAX_SAFE_INTEGER)
3035
serviceMocks.mockHttpTransactionRequest(
3136
TRACE_COLLECTOR_API_URL,
3237
TRACE_API_KEY_TEST,

e2e/utils/serviceMocks.js

+12
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ function mockRpmMetricsRequest (url, apiKey, serviceKey, maxTimes, callback) {
4242
.reply(callback || 200)
4343
}
4444

45+
function mockEdgeMetricsRequest (url, apiKey, serviceKey, maxTimes, callback) {
46+
return nock(url, {
47+
reqheaders: {
48+
'Authorization': 'Bearer ' + apiKey
49+
}
50+
})
51+
.post('/service/42/edge-metrics')
52+
.times(maxTimes)
53+
.reply(callback || 200)
54+
}
55+
4556
function mockHttpTransactionRequest (url, apiKey, callback) {
4657
return nock(url, {
4758
reqheaders: {
@@ -56,5 +67,6 @@ module.exports = {
5667
mockServiceKeyRequest: mockServiceKeyRequest,
5768
mockApmMetricsRequest: mockApmMetricsRequest,
5869
mockRpmMetricsRequest: mockRpmMetricsRequest,
70+
mockEdgeMetricsRequest: mockEdgeMetricsRequest,
5971
mockHttpTransactionRequest: mockHttpTransactionRequest
6072
}

lib/agent/api/index.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function CollectorApi (options) {
1515
this.COLLECTOR_API_SERVICE = url.resolve(options.collectorApiUrl, options.collectorApiServiceEndpoint)
1616
this.COLLECTOR_API_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiApmMetricsEndpoint)
1717
this.COLLECTOR_API_RPM_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiRpmMetricsEndpoint)
18+
this.COLLECTOR_API_EDGE_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiEdgeMetricsEndpoint)
1819

1920
this.apiKey = options.apiKey
2021
this.processId = options.processId
@@ -38,6 +39,13 @@ CollectorApi.prototype.sendSync = function (data) {
3839
})
3940
}
4041

42+
CollectorApi.prototype._sendWithPid = function (destinationUrl, data) {
43+
this._send(destinationUrl, assign({
44+
hostname: this.hostname,
45+
pid: this.processId
46+
}, data))
47+
}
48+
4149
CollectorApi.prototype._send = function (destinationUrl, data) {
4250
var opts = url.parse(destinationUrl)
4351
var payload = JSON.stringify(data)
@@ -77,7 +85,7 @@ CollectorApi.prototype.sendRpmMetrics = function (data) {
7785
}
7886

7987
var url = util.format(this.COLLECTOR_API_RPM_METRICS, this.serviceKey)
80-
this._send(url, assign({ hostname: this.hostname, pid: this.processId }, data))
88+
this._sendWithPid(url, data)
8189
}
8290

8391
CollectorApi.prototype.sendApmMetrics = function (data) {
@@ -87,12 +95,22 @@ CollectorApi.prototype.sendApmMetrics = function (data) {
8795
}
8896

8997
var url = util.format(this.COLLECTOR_API_METRICS, this.serviceKey)
90-
this._send(url, assign({ hostname: this.hostname, pid: this.processId }, data))
98+
this._sendWithPid(url, data)
99+
}
100+
101+
CollectorApi.prototype.sendEdgeMetrics = function (data) {
102+
if (isNaN(this.serviceKey)) {
103+
debug('Service id not present, cannot send metrics')
104+
return
105+
}
106+
107+
var url = util.format(this.COLLECTOR_API_EDGE_METRICS, this.serviceKey)
108+
this._sendWithPid(url, data)
91109
}
92110

93111
CollectorApi.prototype.sendSamples = function (data) {
94112
var url = this.COLLECTOR_API_SAMPLE
95-
this._send(url, assign({ hostname: this.hostname, pid: this.processId }, data))
113+
this._sendWithPid(url, data)
96114
}
97115

98116
CollectorApi.prototype._getRetryInterval = function () {

lib/agent/api/index.spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ describe('The Trace CollectorApi module', function () {
1717
collectorApiApmMetricsEndpoint: '/service/%s/apm-metrics',
1818
collectorApiRpmMetricsEndpoint: '/service/%s/rpm-metrics',
1919
hostname: 'test.org',
20-
processId: '7777'
20+
processId: '7777',
21+
collectorApiEdgeMetricsEndpoint: '/service/%s/edge-metrics'
2122
}
2223

2324
it('can be instantiated w/ serviceName and apiKey', function () {

lib/agent/index.js

+28-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ function Agent (options) {
4040
config: this.config
4141
})
4242

43+
this.edgeMetrics = Metrics.edge.create({
44+
collectorApi: this.collectorApi,
45+
config: this.config
46+
})
47+
4348
this.collectorApi.getService(function (err, serviceKey) {
4449
if (err) {
4550
return debug(err.message)
@@ -83,7 +88,7 @@ Agent.prototype.serverReceive = function (data) {
8388
span.parent = parentId
8489
span.events.push({
8590
id: spanId,
86-
time: data.time || microtime.now(),
91+
time: data.time || this.getMicrotime(),
8792
type: Agent.SERVER_RECV
8893
})
8994
}
@@ -100,7 +105,7 @@ Agent.prototype.serverSend = function (data) {
100105
span.statusCode = data.statusCode
101106
span.events.push({
102107
id: spanId,
103-
time: data.time || microtime.now(),
108+
time: data.time || this.getMicrotime(),
104109
type: Agent.SERVER_SEND
105110
})
106111

@@ -123,7 +128,7 @@ Agent.prototype.clientSend = function (data) {
123128
return
124129
}
125130

126-
data.time = data.time || microtime.now()
131+
data.time = data.time || this.getMicrotime()
127132

128133
if (data.err) {
129134
span.isForceSampled = true
@@ -156,11 +161,23 @@ Agent.prototype.clientSend = function (data) {
156161
Agent.prototype.clientReceive = function (data) {
157162
var span = this.findSpan(data.id)
158163

164+
this.edgeMetrics.report({
165+
targetHost: data.host,
166+
targetServiceKey: data.targetServiceKey,
167+
protocol: data.protocol,
168+
networkDelay: {
169+
incoming: data.networkDelayIncoming,
170+
outgoing: data.networkDelayOutgoing
171+
},
172+
status: data.status,
173+
responseTime: data.responseTime
174+
})
175+
159176
if (!span) {
160177
return
161178
}
162179

163-
data.time = data.time || microtime.now()
180+
data.time = data.time || this.getMicrotime()
164181

165182
span.events.push({
166183
host: data.host,
@@ -183,7 +200,7 @@ Agent.prototype.onCrash = function (data) {
183200
span.isForceSampled = true
184201

185202
span.events.push({
186-
time: microtime.now(),
203+
time: this.getMicrotime(),
187204
id: spanId,
188205
type: 'err',
189206
data: {
@@ -214,7 +231,7 @@ Agent.prototype.report = function (name, userData) {
214231

215232
var dataToSend = {
216233
id: spanId,
217-
time: microtime.now(),
234+
time: this.getMicrotime(),
218235
type: 'us',
219236
data: {
220237
name: name,
@@ -249,7 +266,7 @@ Agent.prototype.reportError = function (errorName, error) {
249266

250267
var dataToSend = {
251268
id: spanId,
252-
time: microtime.now(),
269+
time: this.getMicrotime(),
253270
type: 'us',
254271
data: {
255272
name: errorName,
@@ -354,6 +371,10 @@ Agent.prototype.generateId = function () {
354371
return uuid.v4()
355372
}
356373

374+
Agent.prototype.getMicrotime = function () {
375+
return microtime.now()
376+
}
377+
357378
Agent.prototype._send = function (options) {
358379
debug('sending logs to the trace service')
359380
if (this.spans.length > 0) {

lib/agent/metrics/edge/index.js

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
var consts = require('../../../consts')
2+
3+
function EdgeMetrics (options) {
4+
var _this = this
5+
this.collectorApi = options.collectorApi
6+
this.config = options.config
7+
this.collectInterval = this.config.collectInterval
8+
9+
// metrics
10+
this.metrics = {}
11+
12+
this.interval = setInterval(function () {
13+
_this.sendMetrics()
14+
}, this.collectInterval)
15+
}
16+
17+
EdgeMetrics.prototype.initHost = function (data) {
18+
if (!this.metrics[data.protocol][data.targetHost]) {
19+
this.metrics[data.protocol][data.targetHost] = {
20+
targetServiceKey: data.targetServiceKey,
21+
responseTime: [],
22+
networkDelayIncoming: [],
23+
networkDelayOutgoing: [],
24+
status: {
25+
ok: 0,
26+
notOk: 0
27+
}
28+
}
29+
}
30+
return this.metrics[data.protocol][data.targetHost]
31+
}
32+
33+
EdgeMetrics.prototype.initProtocol = function (data) {
34+
if (!this.metrics[data.protocol]) {
35+
this.metrics[data.protocol] = {}
36+
}
37+
return this.metrics[data.protocol]
38+
}
39+
40+
EdgeMetrics.prototype.report = function (data) {
41+
this.initProtocol(data)
42+
var edge = this.initHost(data)
43+
44+
if (data.networkDelay.incoming) {
45+
edge.networkDelayIncoming.push(data.networkDelay.incoming)
46+
}
47+
48+
if (data.networkDelay.outgoing) {
49+
edge.networkDelayOutgoing.push(data.networkDelay.outgoing)
50+
}
51+
52+
edge.responseTime.push(data.responseTime)
53+
54+
if (data.status === consts.EDGE_STATUS.OK) {
55+
edge.status.ok += 1
56+
} else if (data.status === consts.EDGE_STATUS.NOT_OK) {
57+
edge.status.notOk += 1
58+
}
59+
}
60+
61+
EdgeMetrics.prototype.calculateTimes = function (items) {
62+
var sorted = items.sort(function (a, b) {
63+
return a - b
64+
})
65+
66+
var medianElementIndex = Math.round(sorted.length / 2) - 1
67+
var ninetyFiveElementIndex = Math.round(sorted.length * 0.95) - 1
68+
69+
return {
70+
median: sorted[medianElementIndex],
71+
ninetyFive: sorted[ninetyFiveElementIndex]
72+
}
73+
}
74+
75+
EdgeMetrics.prototype.sendMetrics = function () {
76+
var _this = this
77+
78+
var metrics = Object.keys(this.metrics).map(function (protocol) {
79+
var targetHosts = Object.keys(_this.metrics[protocol]).map(function (hostName) {
80+
var host = _this.metrics[protocol][hostName]
81+
return {
82+
name: hostName,
83+
metrics: {
84+
targetServiceKey: host.targetServiceKey,
85+
responseTime: _this.calculateTimes(host.responseTime),
86+
networkDelayIncoming: _this.calculateTimes(host.networkDelayIncoming),
87+
networkDelayOutgoing: _this.calculateTimes(host.networkDelayOutgoing),
88+
status: {
89+
ok: host.status.ok,
90+
notOk: host.status.notOk
91+
}
92+
}
93+
}
94+
})
95+
96+
return {
97+
protocol: protocol,
98+
targetHosts: targetHosts
99+
}
100+
})
101+
102+
this.metrics = {}
103+
104+
// if no metrics, don't send anything
105+
if (!metrics || !metrics.length) {
106+
return
107+
}
108+
109+
this.collectorApi.sendEdgeMetrics({
110+
timestamp: (new Date()).toISOString(),
111+
hostMetrics: metrics
112+
})
113+
}
114+
115+
function create (options) {
116+
return new EdgeMetrics(options)
117+
}
118+
119+
module.exports.create = create

0 commit comments

Comments
 (0)