Skip to content

Commit 266671a

Browse files
bigmontzrobsdedude
andauthored
Introduce API Metrics/Telemetry (#1142)
Neo4j would like to be able to track more information about driver apis usage, so that smarter decisions can be made on improving drivers and their stack. The collection of metrics must be respectful of users. Thus, the collected metrics are: 1. impossible to be tied back the customer or user 1. transparent to the users that inspect 1. restrained to required metrics The metric collection is disabled by default in Neo4j. It can be enabled in the server by setting `server.bolt.telemetry.enabled` to `true`. However, the metric collection is enabled by default in the drivers. It can be disabled in the driver by configuring the driver with `telemetryDisabled` to `true`. Default: `false`. **Metrics are only collected when enabled both in server and driver instances.** Disabling metrics on driver: ```typescript const driver = neo4j.driver( 'neo4j://localhost:7687', neo4j.auth.basic('neo4j', 'password'), { telemetryDisabled: true }) ``` Co-authored-by: Robsdedude <dev@rouvenbauer.de>
1 parent b774cf3 commit 266671a

File tree

70 files changed

+2786
-177
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2786
-177
lines changed

packages/bolt-connection/src/bolt/bolt-protocol-v1.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Chunker } from '../channel'
2727
import { structure, v1 } from '../packstream'
2828
import RequestMessage, { SIGNATURES } from './request-message'
2929
import {
30+
CompletedObserver,
3031
LoginObserver,
3132
LogoffObserver,
3233
ResetObserver,
@@ -454,6 +455,24 @@ export default class BoltProtocol {
454455
return observer
455456
}
456457

458+
/**
459+
* Send a TELEMETRY through the underlying connection.
460+
*
461+
* @param {object} param0 Message params
462+
* @param {number} param0.api The API called
463+
* @param {object} param1 Configuration and callbacks
464+
* @param {function()} param1.onCompleted Called when completed
465+
* @param {function()} param1.onError Called when error
466+
* @return {StreamObserver} the stream observer that monitors the corresponding server response.
467+
*/
468+
telemetry ({ api }, { onError, onCompleted } = {}) {
469+
const observer = new CompletedObserver()
470+
if (onCompleted) {
471+
onCompleted()
472+
}
473+
return observer
474+
}
475+
457476
_createPacker (chunker) {
458477
return new v1.Packer(chunker)
459478
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
import BoltProtocolV5x3 from './bolt-protocol-v5x3'
20+
21+
import transformersFactories from './bolt-protocol-v5x4.transformer'
22+
import RequestMessage from './request-message'
23+
import { TelemetryObserver } from './stream-observers'
24+
import Transformer from './transformer'
25+
26+
import { internal } from 'neo4j-driver-core'
27+
28+
const {
29+
constants: { BOLT_PROTOCOL_V5_4 }
30+
} = internal
31+
32+
export default class BoltProtocol extends BoltProtocolV5x3 {
33+
get version () {
34+
return BOLT_PROTOCOL_V5_4
35+
}
36+
37+
get transformer () {
38+
if (this._transformer === undefined) {
39+
this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log)))
40+
}
41+
return this._transformer
42+
}
43+
44+
/**
45+
* Send a TELEMETRY through the underlying connection.
46+
*
47+
* @param {object} param0 Message params
48+
* @param {number} param0.api The API called
49+
* @param {object} param1 Configuration and callbacks callbacks
50+
* @param {function()} param1.onCompleted Called when completed
51+
* @param {function()} param1.onError Called when error
52+
* @return {StreamObserver} the stream observer that monitors the corresponding server response.
53+
*/
54+
telemetry ({ api }, { onError, onCompleted } = {}) {
55+
const observer = new TelemetryObserver({ onCompleted, onError })
56+
57+
this.write(RequestMessage.telemetry({ api }), observer, false)
58+
59+
return observer
60+
}
61+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import v5x3 from './bolt-protocol-v5x3.transformer'
21+
22+
export default {
23+
...v5x3
24+
}

packages/bolt-connection/src/bolt/create.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import BoltProtocolV5x0 from './bolt-protocol-v5x0'
3030
import BoltProtocolV5x1 from './bolt-protocol-v5x1'
3131
import BoltProtocolV5x2 from './bolt-protocol-v5x2'
3232
import BoltProtocolV5x3 from './bolt-protocol-v5x3'
33+
import BoltProtocolV5x4 from './bolt-protocol-v5x4'
3334
// eslint-disable-next-line no-unused-vars
3435
import { Chunker, Dechunker } from '../channel'
3536
import ResponseHandler from './response-handler'
@@ -222,6 +223,14 @@ function createProtocol (
222223
log,
223224
onProtocolError,
224225
serversideRouting)
226+
case 5.4:
227+
return new BoltProtocolV5x4(server,
228+
chunker,
229+
packingConfig,
230+
createResponseHandler,
231+
log,
232+
onProtocolError,
233+
serversideRouting)
225234
default:
226235
throw newError('Unknown Bolt protocol version: ' + version)
227236
}

packages/bolt-connection/src/bolt/handshake.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function parseNegotiatedResponse (buffer, log) {
7878
*/
7979
function newHandshakeBuffer () {
8080
return createHandshakeMessage([
81-
[version(5, 3), version(5, 0)],
81+
[version(5, 4), version(5, 0)],
8282
[version(4, 4), version(4, 2)],
8383
version(4, 1),
8484
version(3, 0)

packages/bolt-connection/src/bolt/request-message.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const GOODBYE = 0x02 // 0000 0010 // GOODBYE
3838
const BEGIN = 0x11 // 0001 0001 // BEGIN <metadata>
3939
const COMMIT = 0x12 // 0001 0010 // COMMIT
4040
const ROLLBACK = 0x13 // 0001 0011 // ROLLBACK
41+
42+
const TELEMETRY = 0x54 // 0101 0100 // TELEMETRY <api>
43+
4144
const ROUTE = 0x66 // 0110 0110 // ROUTE
4245

4346
const LOGON = 0x6A // LOGON
@@ -61,6 +64,7 @@ const SIGNATURES = Object.freeze({
6164
BEGIN,
6265
COMMIT,
6366
ROLLBACK,
67+
TELEMETRY,
6468
ROUTE,
6569
LOGON,
6670
LOGOFF,
@@ -367,6 +371,15 @@ export default class RequestMessage {
367371
)
368372
}
369373

374+
static telemetry ({ api }) {
375+
const parsedApi = int(api)
376+
return new RequestMessage(
377+
TELEMETRY,
378+
[parsedApi],
379+
() => `TELEMETRY ${parsedApi.toString()}`
380+
)
381+
}
382+
370383
/**
371384
* Generate the ROUTE message, this message is used to fetch the routing table from the server
372385
*

packages/bolt-connection/src/bolt/stream-observers.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,38 @@ class ResetObserver extends StreamObserver {
526526
}
527527
}
528528

529+
class TelemetryObserver extends ResultStreamObserver {
530+
/**
531+
*
532+
* @param {Object} param -
533+
* @param {function(err: Error)} param.onError
534+
* @param {function(metadata)} param.onCompleted
535+
*/
536+
constructor ({ onError, onCompleted } = {}) {
537+
super()
538+
this._onError = onError
539+
this._onCompleted = onCompleted
540+
}
541+
542+
onNext (record) {
543+
this.onError(
544+
newError('Received RECORD when sending telemetry ' + json.stringify(record), PROTOCOL_ERROR)
545+
)
546+
}
547+
548+
onError (error) {
549+
if (this._onError) {
550+
this._onError(error)
551+
}
552+
}
553+
554+
onCompleted (metadata) {
555+
if (this._onCompleted) {
556+
this._onCompleted(metadata)
557+
}
558+
}
559+
}
560+
529561
class FailedObserver extends ResultStreamObserver {
530562
constructor ({ error, onError }) {
531563
super({ beforeError: onError })
@@ -708,5 +740,6 @@ export {
708740
FailedObserver,
709741
CompletedObserver,
710742
RouteObserver,
711-
ProcedureRouteObserver
743+
ProcedureRouteObserver,
744+
TelemetryObserver
712745
}

packages/bolt-connection/src/connection/connection-channel.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ export function createChannelConnection (
8787
serversideRouting,
8888
chunker,
8989
config.notificationFilter,
90-
createProtocol
90+
createProtocol,
91+
config.telemetryDisabled
9192
)
9293

9394
// forward all pending bytes to the dechunker
@@ -121,7 +122,8 @@ export default class ChannelConnection extends Connection {
121122
serversideRouting = null,
122123
chunker, // to be removed,
123124
notificationFilter,
124-
protocolSupplier
125+
protocolSupplier,
126+
telemetryDisabled
125127
) {
126128
super(errorHandler)
127129
this._authToken = null
@@ -137,6 +139,8 @@ export default class ChannelConnection extends Connection {
137139
this._log = createConnectionLogger(this, log)
138140
this._serversideRouting = serversideRouting
139141
this._notificationFilter = notificationFilter
142+
this._telemetryDisabledDriverConfig = telemetryDisabled === true
143+
this._telemetryDisabledConnection = true
140144

141145
// connection from the database, returned in response for HELLO message and might not be available
142146
this._dbConnectionId = null
@@ -157,13 +161,31 @@ export default class ChannelConnection extends Connection {
157161
}
158162

159163
beginTransaction (config) {
164+
this._sendTelemetryIfEnabled(config)
160165
return this._protocol.beginTransaction(config)
161166
}
162167

163168
run (query, parameters, config) {
169+
this._sendTelemetryIfEnabled(config)
164170
return this._protocol.run(query, parameters, config)
165171
}
166172

173+
_sendTelemetryIfEnabled (config) {
174+
if (this._telemetryDisabledConnection ||
175+
this._telemetryDisabledDriverConfig ||
176+
config == null ||
177+
config.apiTelemetryConfig == null) {
178+
return
179+
}
180+
181+
this._protocol.telemetry({
182+
api: config.apiTelemetryConfig.api
183+
}, {
184+
onCompleted: config.apiTelemetryConfig.onTelemetrySuccess,
185+
onError: config.beforeError
186+
})
187+
}
188+
167189
commitTransaction (config) {
168190
return this._protocol.commitTransaction(config)
169191
}
@@ -290,6 +312,11 @@ export default class ChannelConnection extends Connection {
290312
)
291313
}
292314
}
315+
316+
const telemetryEnabledHint = metadata.hints['telemetry.enabled']
317+
if (telemetryEnabledHint === true) {
318+
this._telemetryDisabledConnection = false
319+
}
293320
}
294321
}
295322
resolve(self)

packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v1.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`#unit BoltProtocolV1 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
3+
exports[`#unit BoltProtocolV1 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
44

5-
exports[`#unit BoltProtocolV1 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
5+
exports[`#unit BoltProtocolV1 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
66

7-
exports[`#unit BoltProtocolV1 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
7+
exports[`#unit BoltProtocolV1 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
88

9-
exports[`#unit BoltProtocolV1 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
9+
exports[`#unit BoltProtocolV1 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
1010

1111
exports[`#unit BoltProtocolV1 .packable() should pack types introduced afterwards as Map (Date) 1`] = `
1212
{

packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v2.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`#unit BoltProtocolV2 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
3+
exports[`#unit BoltProtocolV2 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
44

5-
exports[`#unit BoltProtocolV2 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
5+
exports[`#unit BoltProtocolV2 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
66

7-
exports[`#unit BoltProtocolV2 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
7+
exports[`#unit BoltProtocolV2 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
88

9-
exports[`#unit BoltProtocolV2 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
9+
exports[`#unit BoltProtocolV2 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
1010

1111
exports[`#unit BoltProtocolV2 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`;
1212

packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v3.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`#unit BoltProtocolV3 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
3+
exports[`#unit BoltProtocolV3 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
44

5-
exports[`#unit BoltProtocolV3 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
5+
exports[`#unit BoltProtocolV3 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
66

7-
exports[`#unit BoltProtocolV3 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
7+
exports[`#unit BoltProtocolV3 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
88

9-
exports[`#unit BoltProtocolV3 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
9+
exports[`#unit BoltProtocolV3 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
1010

1111
exports[`#unit BoltProtocolV3 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`;
1212

packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v4x0.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`#unit BoltProtocolV4x0 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
3+
exports[`#unit BoltProtocolV4x0 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
44

5-
exports[`#unit BoltProtocolV4x0 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
5+
exports[`#unit BoltProtocolV4x0 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
66

7-
exports[`#unit BoltProtocolV4x0 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
7+
exports[`#unit BoltProtocolV4x0 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
88

9-
exports[`#unit BoltProtocolV4x0 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
9+
exports[`#unit BoltProtocolV4x0 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
1010

1111
exports[`#unit BoltProtocolV4x0 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`;
1212

packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v4x1.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`#unit BoltProtocolV4x1 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
3+
exports[`#unit BoltProtocolV4x1 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
44

5-
exports[`#unit BoltProtocolV4x1 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
5+
exports[`#unit BoltProtocolV4x1 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
66

7-
exports[`#unit BoltProtocolV4x1 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
7+
exports[`#unit BoltProtocolV4x1 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
88

9-
exports[`#unit BoltProtocolV4x1 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
9+
exports[`#unit BoltProtocolV4x1 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
1010

1111
exports[`#unit BoltProtocolV4x1 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`;
1212

0 commit comments

Comments
 (0)