diff --git a/README.md b/README.md index b63680c..7457ab6 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ Environment Variable | Description | Default | `SW_AGENT_SECURE` | Whether to use secure connection to backend OAP server | `false` | | `SW_AGENT_AUTHENTICATION` | The authentication token to verify that the agent is trusted by the backend OAP, as for how to configure the backend, refer to [the yaml](https://github.com/apache/skywalking/blob/4f0f39ffccdc9b41049903cc540b8904f7c9728e/oap-server/server-bootstrap/src/main/resources/application.yml#L155-L158). | not set | | `SW_AGENT_LOGGING_LEVEL` | The logging level, could be one of `error`, `warn`, `info`, `debug` | `info` | -| `SW_AGENT_DISABLE_PLUGINS` | Comma-delimited list of plugins to disable in the plugins directory (e.g. "mysql", "express"). | `` | +| `SW_AGENT_DISABLE_PLUGINS` | Comma-delimited list of plugins to disable in the plugins directory (e.g. "mysql", "express") | `` | +| `SW_COLD_ENDPOINT` | Cold start detection is as follows: First span to run within 1 second of skywalking init is considered a cold start. This span gets the tag `coldStart` set to 'true'. This span also optionally gets the text '\' appended to the endpoint name if SW_COLD_ENDPOINT is set to 'true'. | `false` | | `SW_IGNORE_SUFFIX` | The suffices of endpoints that will be ignored (not traced), comma separated | `.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg` | | `SW_TRACE_IGNORE_PATH` | The paths of endpoints that will be ignored (not traced), comma separated | `` | | `SW_HTTP_IGNORE_METHOD` | Comma-delimited list of http methods to ignore (GET, POST, HEAD, OPTIONS, etc...) | `` | diff --git a/src/Tag.ts b/src/Tag.ts index 44e30ff..8009ce6 100644 --- a/src/Tag.ts +++ b/src/Tag.ts @@ -24,6 +24,7 @@ export interface Tag { } export default { + coldStartKey: 'coldStart', httpStatusCodeKey: 'http.status.code', // TODO: maybe find a better place to put these? httpStatusMsgKey: 'http.status.msg', httpURLKey: 'http.url', @@ -37,6 +38,13 @@ export default { mqTopicKey: 'mq.topic', mqQueueKey: 'mq.queue', + coldStart(val: boolean = true): Tag { + return { + key: this.coldStartKey, + overridable: true, + val: `${val}`, + } as Tag; + }, httpStatusCode(val: string | number | undefined): Tag { return { key: this.httpStatusCodeKey, diff --git a/src/azure/AzureHttpTriggerPlugin.ts b/src/azure/AzureHttpTriggerPlugin.ts index 30dafad..0e3bdb7 100644 --- a/src/azure/AzureHttpTriggerPlugin.ts +++ b/src/azure/AzureHttpTriggerPlugin.ts @@ -49,7 +49,7 @@ class AzureHttpTriggerPlugin { : ContextManager.current.newEntrySpan(operation, carrier); span.layer = SpanLayer.HTTP; - span.component = Component.AZURE_HTTPTRIGGER ; + span.component = Component.AZURE_HTTPTRIGGER; span.peer = (req.headers['x-forwarded-for'] || '???').split(',').shift(); span.tag(Tag.httpMethod(req.method)); diff --git a/src/config/AgentConfig.ts b/src/config/AgentConfig.ts index 59c2c04..6a267cb 100644 --- a/src/config/AgentConfig.ts +++ b/src/config/AgentConfig.ts @@ -26,6 +26,7 @@ export type AgentConfig = { secure?: boolean; authorization?: string; maxBufferSize?: number; + coldEndpoint?: boolean; disablePlugins?: string; ignoreSuffix?: string; traceIgnorePath?: string; @@ -66,10 +67,11 @@ const _config = { return os.hostname(); })(), collectorAddress: process.env.SW_AGENT_COLLECTOR_BACKEND_SERVICES || '127.0.0.1:11800', - secure: process.env.SW_AGENT_SECURE?.toLocaleLowerCase() === 'true', + secure: process.env.SW_AGENT_SECURE?.toLowerCase() === 'true', authorization: process.env.SW_AGENT_AUTHENTICATION, maxBufferSize: Number.isSafeInteger(process.env.SW_AGENT_MAX_BUFFER_SIZE) ? Number.parseInt(process.env.SW_AGENT_MAX_BUFFER_SIZE as string, 10) : 1000, + coldEndpoint: process.env.SW_COLD_ENDPOINT?.toLowerCase() === 'true', disablePlugins: process.env.SW_AGENT_DISABLE_PLUGINS || '', ignoreSuffix: process.env.SW_IGNORE_SUFFIX ?? '.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg', traceIgnorePath: process.env.SW_TRACE_IGNORE_PATH || '', diff --git a/src/trace/context/ContextManager.ts b/src/trace/context/ContextManager.ts index fdad6ab..b0ec89f 100644 --- a/src/trace/context/ContextManager.ts +++ b/src/trace/context/ContextManager.ts @@ -59,6 +59,19 @@ if (async_hooks.AsyncLocalStorage) { } class ContextManager { + isCold = true; + + cosntructor() { + setTimeout(() => this.isCold = false, 1000).unref(); + } + + checkCold(): boolean { + const isCold = this.isCold; + this.isCold = false; + + return isCold; + } + get asyncState(): AsyncState { let asyncState = store.getStore(); diff --git a/src/trace/context/DummyContext.ts b/src/trace/context/DummyContext.ts index 2fe73ab..2acf661 100644 --- a/src/trace/context/DummyContext.ts +++ b/src/trace/context/DummyContext.ts @@ -42,10 +42,12 @@ export default class DummyContext implements Context { return DummySpan.create(this); } - start(span: Span): Context { + start(span: DummySpan): Context { const spans = ContextManager.spansDup(); if (!this.nSpans++) { + ContextManager.checkCold(); // set cold to false + if (spans.indexOf(span) === -1) spans.push(span); } @@ -53,7 +55,7 @@ export default class DummyContext implements Context { return this; } - stop(span: Span): boolean { + stop(span: DummySpan): boolean { if (--this.nSpans) return false; @@ -62,11 +64,11 @@ export default class DummyContext implements Context { return true; } - async(span: Span) { + async(span: DummySpan) { ContextManager.clear(span); } - resync(span: Span) { + resync(span: DummySpan) { ContextManager.restore(span); } } diff --git a/src/trace/context/SpanContext.ts b/src/trace/context/SpanContext.ts index 7a19781..79d39e0 100644 --- a/src/trace/context/SpanContext.ts +++ b/src/trace/context/SpanContext.ts @@ -27,6 +27,7 @@ import ExitSpan from '../../trace/span/ExitSpan'; import LocalSpan from '../../trace/span/LocalSpan'; import SegmentRef from './SegmentRef'; import ContextManager from './ContextManager'; +import Tag from '../../Tag'; import { Component } from '../../trace/Component'; import { createLogger } from '../../logging'; import { ContextCarrier } from './ContextCarrier'; @@ -173,8 +174,13 @@ export default class SpanContext implements Context { nSpans: this.nSpans, }); - if (!this.nSpans++) + if (!this.nSpans++) { SpanContext.nActiveSegments += 1; + span.isCold = ContextManager.checkCold(); + + if (span.isCold) + span.tag(Tag.coldStart(), true); + } if (spans.indexOf(span) === -1) spans.push(span); diff --git a/src/trace/span/Span.ts b/src/trace/span/Span.ts index a240c8a..e5fc638 100644 --- a/src/trace/span/Span.ts +++ b/src/trace/span/Span.ts @@ -17,6 +17,7 @@ * */ +import config from '../../config/AgentConfig'; import Context from '../../trace/context/Context'; import { Component } from '../Component'; import { Tag } from '../../Tag'; @@ -51,6 +52,7 @@ export default abstract class Span { layer = SpanLayer.UNKNOWN; component = Component.UNKNOWN; depth = 0; + isCold = false; inherit?: Component; readonly tags: Tag[] = []; @@ -95,6 +97,9 @@ export default abstract class Span { } finish(segment: Segment): boolean { + if (this.isCold && config.coldEndpoint) + this.operation = this.operation + ''; + this.endTime = new Date().getTime(); segment.archive(this); return true; @@ -118,19 +123,24 @@ export default abstract class Span { return !this.tags.every((t) => t.key !== key); } - tag(tag: Tag): this { - if (!tag.overridable) { - this.tags.push(Object.assign({}, tag)); - } + tag(tag: Tag, insert?: boolean): this { + if (tag.overridable) { + const sameTags = this.tags.filter((it) => it.key === tag.key); - const sameTags = this.tags.filter((it) => it.key === tag.key); + if (sameTags.length) { + sameTags.forEach((it) => (it.val = tag.val)); - if (sameTags.length) { - sameTags.forEach((it) => (it.val = tag.val)); - } else { - this.tags.push(Object.assign({}, tag)); + return this; + } } + const tagObj = Object.assign({}, tag); + + if (!insert) + this.tags.push(tagObj); + else + this.tags.unshift(tagObj) + return this; } diff --git a/tests/plugins/axios/expected.data.yaml b/tests/plugins/axios/expected.data.yaml index c50deae..cb0046f 100644 --- a/tests/plugins/axios/expected.data.yaml +++ b/tests/plugins/axios/expected.data.yaml @@ -53,6 +53,8 @@ segmentItems: peer: not null skipAnalysis: false tags: + - key: coldStart + value: 'true' - key: http.url value: http://server:5000/axios - key: http.method @@ -101,6 +103,8 @@ segmentItems: spanId: 0 spanLayer: Http tags: + - key: coldStart + value: 'true' - key: http.url value: http://localhost:5001/axios - key: http.method diff --git a/tests/plugins/express/expected.data.yaml b/tests/plugins/express/expected.data.yaml index 1595e5d..6924540 100644 --- a/tests/plugins/express/expected.data.yaml +++ b/tests/plugins/express/expected.data.yaml @@ -27,6 +27,8 @@ segmentItems: spanId: 0 spanLayer: Http tags: + - key: coldStart + value: 'true' - key: http.url value: http://server:5000/express - key: http.method @@ -81,6 +83,8 @@ segmentItems: spanId: 0 spanLayer: Http tags: + - key: coldStart + value: 'true' - key: http.url value: http://localhost:5001/express - key: http.method diff --git a/tests/plugins/http/expected.data.yaml b/tests/plugins/http/expected.data.yaml index 3e9076f..a75586c 100644 --- a/tests/plugins/http/expected.data.yaml +++ b/tests/plugins/http/expected.data.yaml @@ -33,6 +33,8 @@ segmentItems: peer: not null skipAnalysis: false tags: + - key: coldStart + value: 'true' - key: http.url value: http://server:5000/test - key: http.method @@ -87,6 +89,8 @@ segmentItems: peer: not null skipAnalysis: false tags: + - key: coldStart + value: 'true' - key: http.url value: http://localhost:5001/test - key: http.method diff --git a/tests/plugins/mongodb/expected.data.yaml b/tests/plugins/mongodb/expected.data.yaml index 442a27f..5180198 100644 --- a/tests/plugins/mongodb/expected.data.yaml +++ b/tests/plugins/mongodb/expected.data.yaml @@ -63,6 +63,7 @@ segmentItems: peer: not null skipAnalysis: false tags: + - { key: coldStart, value: 'true' } - { key: http.url, value: 'http://server:5000/mongo' } - { key: http.method, value: GET } - { key: http.status.code, value: '200' } @@ -93,6 +94,7 @@ segmentItems: peer: not null skipAnalysis: false tags: + - { key: coldStart, value: 'true' } - { key: http.url, value: 'http://localhost:5001/mongo' } - { key: http.method, value: GET } - { key: http.status.code, value: '200' } diff --git a/tests/plugins/mongoose/expected.data.yaml b/tests/plugins/mongoose/expected.data.yaml index 029e3fa..bc28d7e 100644 --- a/tests/plugins/mongoose/expected.data.yaml +++ b/tests/plugins/mongoose/expected.data.yaml @@ -77,6 +77,7 @@ segmentItems: peer: not null skipAnalysis: false tags: + - { key: coldStart, value: 'true' } - { key: http.url, value: 'http://server:5000/mongoose' } - { key: http.method, value: GET } - { key: http.status.code, value: '200' } @@ -107,6 +108,7 @@ segmentItems: peer: not null skipAnalysis: false tags: + - { key: coldStart, value: 'true' } - { key: http.url, value: 'http://localhost:5001/mongoose' } - { key: http.method, value: GET } - { key: http.status.code, value: '200' } diff --git a/tests/plugins/mysql/expected.data.yaml b/tests/plugins/mysql/expected.data.yaml index 221f4e9..dd297fb 100644 --- a/tests/plugins/mysql/expected.data.yaml +++ b/tests/plugins/mysql/expected.data.yaml @@ -51,6 +51,8 @@ segmentItems: peer: not null skipAnalysis: false tags: + - key: coldStart + value: 'true' - key: http.url value: http://server:5000/mysql - key: http.method @@ -85,6 +87,8 @@ segmentItems: peer: not null skipAnalysis: false tags: + - key: coldStart + value: 'true' - key: http.url value: http://localhost:5001/mysql - key: http.method diff --git a/tests/plugins/pg/expected.data.yaml b/tests/plugins/pg/expected.data.yaml index d35cefb..af77133 100644 --- a/tests/plugins/pg/expected.data.yaml +++ b/tests/plugins/pg/expected.data.yaml @@ -48,6 +48,7 @@ segmentItems: peer: not null skipAnalysis: false tags: + - { key: coldStart, value: 'true' } - { key: http.url, value: 'http://server:5000/postgres' } - { key: http.method, value: GET } - { key: http.status.code, value: '200' } @@ -78,6 +79,7 @@ segmentItems: peer: not null skipAnalysis: false tags: + - { key: coldStart, value: 'true' } - { key: http.url, value: 'http://localhost:5001/postgres' } - { key: http.method, value: GET } - { key: http.status.code, value: '200' }