diff --git a/lib/agent.js b/lib/agent.js index 8a5b7e39..15ae45b0 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -13,7 +13,7 @@ const stringMetaService = require('./context/string-meta-service') const apiMetaService = require('./context/api-meta-service') const Scheduler = require('./utils/scheduler') const AgentStatsMonitor = require('./metric/agent-stats-monitor') -const getConfig = require('./config').getConfig +const { initializeConfig, getConfig } = require('./config') const PinpointClient = require('./client/pinpoint-client') const dataSenderFactory = require('./client/data-sender-factory') const AgentInfo = require('./data/dto/agent-info') @@ -21,8 +21,10 @@ const PinScheduler = require('./metric/ping-scheduler') class Agent { constructor(initOptions) { - this.config = getConfig(initOptions) + initializeConfig(initOptions) + this.config = getConfig() + log.init(this.config.logLevel) log.warn('[Pinpoint Agent] Configuration', this.config) if (!this.config || !this.config.enable || this.config.enable.toString() !== 'true') { diff --git a/lib/client/grpc-data-sender.js b/lib/client/grpc-data-sender.js index df8f7478..ba990562 100644 --- a/lib/client/grpc-data-sender.js +++ b/lib/client/grpc-data-sender.js @@ -61,6 +61,9 @@ class GrpcDataSender { if (this.profilerClient) { this.profilerClient.close() } + if (this.profilerStream) { + this.profilerStream.grpcStream.end() + } } initializeClients(collectorIp, collectorTcpPort, config) { @@ -125,6 +128,7 @@ class GrpcDataSender { profilerBuilder.setGrpcServiceConfig(config.grpcServiceConfig.getProfiler()) } this.profilerClient = new services.ProfilerCommandServiceClient(collectorIp + ":" + collectorTcpPort, grpc.credentials.createInsecure(), profilerBuilder.build()) + this.profilerStream = new GrpcBidirectionalStream('profilerStream', this.profilerClient, this.profilerClient.handleCommandV2) } agentInfoRefreshInterval() { diff --git a/lib/config.js b/lib/config.js index c7184a3f..e4725d27 100644 --- a/lib/config.js +++ b/lib/config.js @@ -89,8 +89,6 @@ const CONFIG_FILE_MAP = { profilerSqlStat: 'profiler-sql-stat' } -let agentConfig = null - const REQUIRE_CONFIG = { agentId: 'an Agent ID', applicationName: 'an Application Name' @@ -237,6 +235,11 @@ const getConfig = (initOptions) => { return agentConfig } +const initializeConfig = (initOptions) => { + clear() + init(initOptions) +} + const clear = () => agentConfig && (agentConfig = null) //https://github.com/sindresorhus/is-docker @@ -261,11 +264,14 @@ function hasDockerCGroup() { } } +let agentConfig = readConfigJson(defaultConfig) + module.exports = { getConfig, clear, readConfigJson, readRootConfigFile, getMainModulePath, - isContainerEnvironment + isContainerEnvironment, + initializeConfig, } diff --git a/lib/context/transaction-id.js b/lib/context/transaction-id.js index fca86e2d..82c66a92 100644 --- a/lib/context/transaction-id.js +++ b/lib/context/transaction-id.js @@ -8,7 +8,7 @@ const transactionIdGenerator = require('./sequence-generators').transactionIdGenerator -const DELIMETER = '^' +const delimiter = '^' class TransactionId { constructor (agentId, agentStartTime, sequence) { @@ -23,12 +23,12 @@ class TransactionId { } toString () { - return [this.agentId, this.agentStartTime, this.sequence].join(DELIMETER) + return [this.agentId, this.agentStartTime, this.sequence].join(delimiter) } static toTransactionId(str) { if (str !== null && str !== undefined) { - const r = str.split(DELIMETER) + const r = str.split(delimiter) if (r.length === 3) { return new TransactionId(r[0], r[1], r[2]) } diff --git a/lib/instrumentation/context/span-builder.js b/lib/instrumentation/context/span-builder.js index ff2a96f9..d8f0099a 100644 --- a/lib/instrumentation/context/span-builder.js +++ b/lib/instrumentation/context/span-builder.js @@ -4,13 +4,16 @@ * Apache License v2.0 */ +const TransactionId = require("../../context/transaction-id") +const TraceId = require("../../context/trace-id") + class SpanBuilder { - constructor(traceId, agentId, applicationName, applicationServiceType, agentStartTime, serviceType, host, parentApplicationName, parentApplicationType) { + constructor(traceId, agentInfo) { this.traceId = traceId - this.agentId = agentId - this.applicationName = applicationName - this.agentStartTime = agentStartTime - this.serviceType = serviceType + this.agentId = agentInfo.agentId + this.applicationName = agentInfo.applicationName + this.agentStartTime = agentInfo.agentStartTime + this.serviceType = agentInfo.serviceType this.spanId = traceId.spanId this.parentSpanId = traceId.parentSpanId this.startTime = Date.now() @@ -24,12 +27,19 @@ class SpanBuilder { this.spanEventList = [] this.apiId = null this.exceptionInfo = null - this.applicationServiceType = applicationServiceType + this.applicationServiceType = agentInfo.applicationServiceType this.loggingTransactionInfo = null this.version = 1 - this.acceptorHost = host - this.parentApplicationName = parentApplicationName - this.parentApplicationType = parentApplicationType + this.acceptorHost = undefined + this.parentApplicationName = undefined + this.parentApplicationType = undefined + } + + static makeSpanBuilderWithSpanId(spanId, agentInfo, parentSpanId = '-1') { + const transactionId = new TransactionId(agentInfo.agentId, agentInfo.agentStartTime) + const traceId = new TraceId(transactionId, spanId, parentSpanId) + const builder = new SpanBuilder(traceId, agentInfo) + return builder } static valueOf(span) { diff --git a/test/client/grpc-data-sender.test.js b/test/client/grpc-data-sender.test.js index 0392b904..9d00d00e 100644 --- a/test/client/grpc-data-sender.test.js +++ b/test/client/grpc-data-sender.test.js @@ -11,6 +11,35 @@ const SpanChunk = require('../../lib/context/span-chunk') const Span = require('../../lib/context/span') const SpanEvent = require('../../lib/context/span-event') const MockGrpcDataSender = require('./mock-grpc-data-sender') +const grpc = require('@grpc/grpc-js') +const services = require('../../lib/data/v1/Service_grpc_pb') +const { beforeSpecificOne, afterOne, getCallRequests, getMetadata, DataSourceCallCountable } = require('./grpc-fixture') + +function sendSpan(call, callback) { + call.on('error', function (error) { + }) + call.on('data', function (spanMessage) { + const span = spanMessage.getSpan() + const callRequests = getCallRequests() + callRequests.push(span) + }) + call.on('end', function () { + }) + const callMetadata = getMetadata() + callMetadata.push(call.metadata) +} + +class DataSource extends DataSourceCallCountable { + constructor(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) { + super(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) + } + + initializeClients() { } + initializeMetadataClients() { } + initializeStatStream() { } + initializePingStream() { } + initializeAgentInfoScheduler() { } +} test('Should send span ', function (t) { const expectedSpan = { @@ -64,7 +93,22 @@ test('Should send span ', function (t) { agentStartTime: 1592574173350 }), expectedSpan) - const grpcDataSender = new MockGrpcDataSender('', 0, 0, 0, {agentId: 'agent', applicationName: 'applicationName', agentStartTime: 1234344}) + const server = new grpc.Server() + server.addService(services.SpanService, { + sendSpan: sendSpan + }) + let dataSender + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + dataSender = beforeSpecificOne(port, DataSource) + dataSender.sendSpan(span) + afterOne(t) + }) + t.teardown(() => { + dataSender.close() + server.forceShutdown() + }) + + const grpcDataSender = new MockGrpcDataSender('', 0, 0, 0, { agentId: 'agent', applicationName: 'applicationName', agentStartTime: 1234344 }) grpcDataSender.sendSpan(span) t.plan(20) @@ -120,7 +164,7 @@ test('Should send span ', function (t) { t.equal(actual.getLoggingtransactioninfo(), 0, 'logging transaction info') }) -const grpcDataSender = new MockGrpcDataSender('', 0, 0, 0, {agentId: 'agent', applicationName: 'applicationName', agentStartTime: 1234344}) +const grpcDataSender = new MockGrpcDataSender('', 0, 0, 0, { agentId: 'agent', applicationName: 'applicationName', agentStartTime: 1234344 }) test('sendSpanChunk redis.SET.end', function (t) { let expectedSpanChunk = { @@ -140,51 +184,51 @@ test('sendSpanChunk redis.SET.end', function (t) { 'sequence': 0 }, 'spanEventList': [Object.assign(new SpanEvent({ - spanId: 7056897257955935, - endPoint: 'localhost:6379' - }, 0), { - 'spanId': 7056897257955935, - 'sequence': 0, - 'startTime': 1592872091543, - 'elapsedTime': 0, - 'startElapsed': 14, - 'serviceType': 100, - 'endPoint': null, - 'annotations': [], - 'depth': 1, - 'nextSpanId': -1, - 'destinationId': null, - 'apiId': 1, - 'exceptionInfo': null, - 'asyncId': null, - 'nextAsyncId': null, - 'asyncSequence': null, - 'dummyId': null, - 'nextDummyId': null - }), - Object.assign(new SpanEvent({ - spanId: 7056897257955935, - endPoint: 'localhost:6379' - }, 1), { - 'spanId': 7056897257955935, - 'sequence': 1, - 'startTime': 1592872091543, - 'elapsedTime': 2, - 'startElapsed': 7, - 'serviceType': 8200, - 'endPoint': 'localhost:6379', - 'annotations': [Annotations.of(annotationKey.API.getCode(), 'redis.SET.end')], - 'depth': 2, - 'nextSpanId': 1508182809976945, - 'destinationId': 'Redis', - 'apiId': 0, - 'exceptionInfo': null, - 'asyncId': null, - 'nextAsyncId': null, - 'asyncSequence': null, - 'dummyId': null, - 'nextDummyId': null - }) + spanId: 7056897257955935, + endPoint: 'localhost:6379' + }, 0), { + 'spanId': 7056897257955935, + 'sequence': 0, + 'startTime': 1592872091543, + 'elapsedTime': 0, + 'startElapsed': 14, + 'serviceType': 100, + 'endPoint': null, + 'annotations': [], + 'depth': 1, + 'nextSpanId': -1, + 'destinationId': null, + 'apiId': 1, + 'exceptionInfo': null, + 'asyncId': null, + 'nextAsyncId': null, + 'asyncSequence': null, + 'dummyId': null, + 'nextDummyId': null + }), + Object.assign(new SpanEvent({ + spanId: 7056897257955935, + endPoint: 'localhost:6379' + }, 1), { + 'spanId': 7056897257955935, + 'sequence': 1, + 'startTime': 1592872091543, + 'elapsedTime': 2, + 'startElapsed': 7, + 'serviceType': 8200, + 'endPoint': 'localhost:6379', + 'annotations': [Annotations.of(annotationKey.API.getCode(), 'redis.SET.end')], + 'depth': 2, + 'nextSpanId': 1508182809976945, + 'destinationId': 'Redis', + 'apiId': 0, + 'exceptionInfo': null, + 'asyncId': null, + 'nextAsyncId': null, + 'asyncSequence': null, + 'dummyId': null, + 'nextDummyId': null + }) ], 'endPoint': null, 'applicationServiceType': 1400, @@ -269,48 +313,48 @@ test('sendSpanChunk redis.GET.end', (t) => { 'sequence': 0 }, 'spanEventList': [Object.assign(new SpanEvent({ - spanId: 7056897257955935, - endPoint: 'localhost:6379' - }, 0), { - 'spanId': 7056897257955935, - 'sequence': 0, - 'startTime': 1592872091543, - 'elapsedTime': 0, - 'startElapsed': 14, - 'serviceType': 100, - 'endPoint': null, - 'annotations': [], - 'depth': 1, - 'nextSpanId': -1, - 'destinationId': null, - 'apiId': 1, - 'exceptionInfo': null, - 'asyncId': null, - 'nextAsyncId': null, - 'asyncSequence': null, - 'dummyId': null, - 'nextDummyId': null - }), - { - 'spanId': 7056897257955935, - 'sequence': 1, - 'startTime': 1592872091543, - 'elapsedTime': 0, - 'startElapsed': 7, - 'serviceType': 8200, - 'endPoint': 'localhost:6379', - 'annotations': [Annotations.of(annotationKey.API.getCode(), 'redis.GET.end')], - 'depth': 2, - 'nextSpanId': 6277978728741477, - 'destinationId': 'Redis', - 'apiId': 0, - 'exceptionInfo': null, - 'asyncId': null, - 'nextAsyncId': null, - 'asyncSequence': null, - 'dummyId': null, - 'nextDummyId': null - } + spanId: 7056897257955935, + endPoint: 'localhost:6379' + }, 0), { + 'spanId': 7056897257955935, + 'sequence': 0, + 'startTime': 1592872091543, + 'elapsedTime': 0, + 'startElapsed': 14, + 'serviceType': 100, + 'endPoint': null, + 'annotations': [], + 'depth': 1, + 'nextSpanId': -1, + 'destinationId': null, + 'apiId': 1, + 'exceptionInfo': null, + 'asyncId': null, + 'nextAsyncId': null, + 'asyncSequence': null, + 'dummyId': null, + 'nextDummyId': null + }), + { + 'spanId': 7056897257955935, + 'sequence': 1, + 'startTime': 1592872091543, + 'elapsedTime': 0, + 'startElapsed': 7, + 'serviceType': 8200, + 'endPoint': 'localhost:6379', + 'annotations': [Annotations.of(annotationKey.API.getCode(), 'redis.GET.end')], + 'depth': 2, + 'nextSpanId': 6277978728741477, + 'destinationId': 'Redis', + 'apiId': 0, + 'exceptionInfo': null, + 'asyncId': null, + 'nextAsyncId': null, + 'asyncSequence': null, + 'dummyId': null, + 'nextDummyId': null + } ], 'endPoint': null, 'applicationServiceType': 1400, diff --git a/test/client/grpc-fixture.js b/test/client/grpc-fixture.js new file mode 100644 index 00000000..33317f10 --- /dev/null +++ b/test/client/grpc-fixture.js @@ -0,0 +1,111 @@ +/** + * Pinpoint Node.js Agent + * Copyright 2020-present NAVER Corp. + * Apache License v2.0 + */ + +'use strict' + +const config = require('../../lib/config') +const AgentInfo = require('../../lib/data/dto/agent-info') +const GrpcDataSender = require('../../lib/client/grpc-data-sender') + +let callCount = 0 +let afterCount = 0 +let callRequests = [] +let callMetadata = [] + +function beforeSpecificOne(port, one, serviceConfig) { + callCount = 0 + afterCount = 0 + config.clear() + callRequests = [] + callMetadata = [] + const actualConfig = config.getConfig({ 'grpc.service_config': serviceConfig }) + actualConfig.collectorIp = 'localhost' + actualConfig.collectorTcpPort = port + actualConfig.collectorStatPort = port + actualConfig.collectorSpanPort = port + actualConfig.enabledDataSending = true + return new one( + actualConfig.collectorIp, + actualConfig.collectorTcpPort, + actualConfig.collectorStatPort, + actualConfig.collectorSpanPort, + agentInfo(), + actualConfig + ) +} + +function agentInfo() { + return Object.assign(new AgentInfo({ + agentId: '12121212', + applicationName: 'applicationName', + agentStartTime: Date.now() + }), { + ip: '1' + }) +} + +function afterOne(t) { + afterCount++ + if (callCount === afterCount) { + t.end() + } +} + +function getCallRequests() { + return callRequests +} + +function getMetadata() { + return callMetadata +} + +function increaseCallCount() { + callCount++ +} + +class DataSourceCallCountable extends GrpcDataSender { + constructor(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) { + super(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) + } + + sendAgentInfo(agentInfo, callArguments) { + increaseCallCount() + super.sendAgentInfo(agentInfo, callArguments) + } + + sendApiMetaInfo(apiMetaInfo, callArguments) { + increaseCallCount() + super.sendApiMetaInfo(apiMetaInfo, callArguments) + } + + sendStringMetaInfo(stringMetaInfo, callArguments) { + increaseCallCount() + super.sendStringMetaInfo(stringMetaInfo, callArguments) + } + + sendSqlMetaInfo(sqlMetaData, callback) { + increaseCallCount() + super.sendSqlMetaInfo(sqlMetaData, callback) + } + + sendSqlUidMetaData(sqlMetaData, callback) { + increaseCallCount() + super.sendSqlUidMetaData(sqlMetaData, callback) + } + + sendSpan(span) { + increaseCallCount() + super.sendSpan(span) + } +} + +module.exports = { + beforeSpecificOne, + afterOne, + getCallRequests, + getMetadata, + DataSourceCallCountable, +} \ No newline at end of file diff --git a/test/client/grpc-unary-rpc.test.js b/test/client/grpc-unary-rpc.test.js index 1919ac9a..7b51c3df 100644 --- a/test/client/grpc-unary-rpc.test.js +++ b/test/client/grpc-unary-rpc.test.js @@ -13,23 +13,20 @@ const spanMessages = require('../../lib/data/v1/Span_pb') const AgentInfo = require('../../lib/data/dto/agent-info') const ApiMetaInfo = require('../../lib/data/dto/api-meta-info') const StringMetaInfo = require('../../lib/data/dto/string-meta-info') -const GrpcDataSender = require('../../lib/client/grpc-data-sender') const MethodDescriptorBuilder = require('../../lib/context/method-descriptor-builder') const CallArgumentsBuilder = require('../../lib/client/call-arguments-builder') const config = require('../../lib/config') const SqlMetaData = require('../../lib/client/sql-meta-data') const sqlMetadataService = require('../../lib/instrumentation/sql/sql-metadata-service') const SqlUidMetaData = require('../../lib/client/sql-uid-meta-data') +const { beforeSpecificOne, afterOne, getCallRequests, getMetadata, DataSourceCallCountable } = require('./grpc-fixture') -let callCount = 0 -let afterCount = 0 -let callRequests = [] -let callMetadata = [] // https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/v5.0.0/examples/src/grpcjs/client.ts const service = (call, callback) => { const succeedOnRetryAttempt = call.metadata.get('succeed-on-retry-attempt') const previousAttempts = call.metadata.get('grpc-previous-rpc-attempts') - + const callRequests = getCallRequests() + const callMetadata = getMetadata() // console.debug(`succeed-on-retry-attempt: ${succeedOnRetryAttempt[0]}, grpc-previous-rpc-attempts: ${previousAttempts[0]}`) if (succeedOnRetryAttempt.length === 0 || (previousAttempts.length > 0 && previousAttempts[0] === succeedOnRetryAttempt[0])) { const result = new spanMessages.PResult() @@ -55,66 +52,6 @@ function agentInfo() { }) } -function beforeSpecificOne(port, one, serviceConfig) { - callCount = 0 - afterCount = 0 - config.clear() - callRequests = [] - callMetadata = [] - const actualConfig = config.getConfig({ 'grpc.service_config': serviceConfig }) - actualConfig.collectorIp = 'localhost' - actualConfig.collectorTcpPort = port - actualConfig.collectorStatPort = port - actualConfig.collectorSpanPort = port - actualConfig.enabledDataSending = true - return new one( - actualConfig.collectorIp, - actualConfig.collectorTcpPort, - actualConfig.collectorStatPort, - actualConfig.collectorSpanPort, - agentInfo(), - actualConfig - ) -} - -function afterOne(t) { - afterCount++ - if (callCount === afterCount) { - t.end() - } -} - -class DataSourceCallCountable extends GrpcDataSender { - constructor(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) { - super(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) - } - - sendAgentInfo(agentInfo, callArguments) { - callCount++ - super.sendAgentInfo(agentInfo, callArguments) - } - - sendApiMetaInfo(apiMetaInfo, callArguments) { - callCount++ - super.sendApiMetaInfo(apiMetaInfo, callArguments) - } - - sendStringMetaInfo(stringMetaInfo, callArguments) { - callCount++ - super.sendStringMetaInfo(stringMetaInfo, callArguments) - } - - sendSqlMetaInfo(sqlMetaData, callback) { - callCount++ - super.sendSqlMetaInfo(sqlMetaData, callback) - } - - sendSqlUidMetaData(sqlMetaData, callback) { - callCount++ - super.sendSqlUidMetaData(sqlMetaData, callback) - } -} - let agentInfoRefreshInterval class AgentInfoOnlyDataSource extends DataSourceCallCountable { constructor(collectorIp, collectorTcpPort, collectorStatPort, collectorSpanPort, agentInfo, config) { @@ -225,6 +162,8 @@ test('sendApiMetaInfo retry', (t) => { let dataSender server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { dataSender = beforeSpecificOne(port, MetaInfoOnlyDataSource) + const callRequests = getCallRequests() + const callMetadata = getMetadata() let actual = new ApiMetaInfo(1, 'ApiDescriptor', MethodType.DEFAULT) let callArguments = new CallArgumentsBuilder(function (error, response) { @@ -269,6 +208,8 @@ test('sendApiMetaInfo lineNumber and location', (t) => { let dataSender server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { dataSender = beforeSpecificOne(port, MetaInfoOnlyDataSource) + const callRequests = getCallRequests() + const callMetadata = getMetadata() let apiMetaInfoActual = ApiMetaInfo.create(new MethodDescriptorBuilder() .setApiId(1) @@ -384,6 +325,7 @@ test('sendSqlMetaData retry', (t) => { let dataSender server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { dataSender = beforeSpecificOne(port, MetaInfoOnlyDataSource) + const callRequests = getCallRequests() const parsingResult = sqlMetadataService.cacheSql('SELECT DATABASE() as res') const actual = new SqlMetaData(parsingResult) @@ -428,6 +370,8 @@ test('sendSqlUidMetaData retry', (t) => { let dataSender server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { dataSender = beforeSpecificOne(port, MetaInfoOnlyDataSource) + const callRequests = getCallRequests() + const callMetadata = getMetadata() const parsingResult = sqlMetadataService.cacheSql('SELECT DATABASE() as res') const actual = new SqlUidMetaData(parsingResult) diff --git a/test/fixture.js b/test/fixture.js index 327b35a5..4809c967 100644 --- a/test/fixture.js +++ b/test/fixture.js @@ -9,6 +9,7 @@ const TraceId = require('../lib/context/trace-id') const IdGenerator = require('../lib/context/id-generator') const shimmer = require('@pinpoint-apm/shimmer') const testConfig= require('./pinpoint-config-test') +require('../lib/config').clear() const config = require('../lib/config').getConfig(testConfig) const { namedGroupLocationFileName, namedGroupTypeMethod } = require('../lib/instrumentation/call-stack') const TraceBuilder = require('../lib/instrumentation/context/trace-builder') diff --git a/test/support/agent-singleton-mock.js b/test/support/agent-singleton-mock.js index 41bab213..79238dae 100644 --- a/test/support/agent-singleton-mock.js +++ b/test/support/agent-singleton-mock.js @@ -113,5 +113,5 @@ class MockAgent extends Agent { } } -const agent = new MockAgent(fixture.config) +const agent = new MockAgent(require('../pinpoint-config-test')) module.exports = agent \ No newline at end of file