Skip to content

Commit

Permalink
feat: Long Tasks instrumentation (#757)
Browse files Browse the repository at this point in the history
Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
  • Loading branch information
t2t2 and vmarchaud committed Dec 8, 2021
1 parent 4a3b410 commit 56d332e
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/component_owners.yml
Expand Up @@ -44,6 +44,9 @@ components:
- rauno56
plugins/web/opentelemetry-instrumentation-document-load:
- obecny
plugins/web/opentelemetry-instrumentation-long-task:
- mhennoch
- t2t2
plugins/web/opentelemetry-instrumentation-user-interaction:
- obecny
propagators/opentelemetry-propagator-aws-xray:
Expand Down
@@ -0,0 +1 @@
build
@@ -0,0 +1,9 @@
module.exports = {
"env": {
"mocha": true,
"commonjs": true,
"browser": true,
"jquery": true
},
...require('../../../eslint.config.js')
}
59 changes: 59 additions & 0 deletions plugins/web/opentelemetry-instrumentation-long-task/README.md
@@ -0,0 +1,59 @@
# OpenTelemetry Long Task Instrumentation for web

[![NPM Published Version][npm-img]][npm-url]
[![dependencies][dependencies-image]][dependencies-url]
[![devDependencies][devDependencies-image]][devDependencies-url]
[![Apache License][license-image]][license-image]

This instrumentation creates spans from tasks that take more than 50 milliseconds using the [Long Task API][mdn-long-task].
All of the data reported via [`PerformanceLongTaskTiming`][mdn-performance-long-task-timing] is included as span attributes.

Compatible with OpenTelemetry JS API and SDK `1.0+`.

## Installation

```bash
npm install --save @opentelemetry/instrumentation-long-task
```

## Usage

```js
import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { LongTaskInstrumentation } from '@opentelemetry/instrumentation-long-task';
import { registerInstrumentations } from '@opentelemetry/instrumentation';

const provider = new WebTracerProvider();

provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));

registerInstrumentations({
tracerProvider: provider,
instrumentations: [
new LongTaskInstrumentation(),
],
});
```

## Useful links

- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more about OpenTelemetry JavaScript: <https://github.com/open-telemetry/opentelemetry-js>
- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]

## License

Apache 2.0 - See [LICENSE][license-url] for more information.

[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions
[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
[dependencies-image]: https://status.david-dm.org/gh/open-telemetry/opentelemetry-js-contrib.svg?path=plugins%2Fweb%2Fopentelemetry-instrumentation-long-task
[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib?path=plugins%2Fweb%2Fopentelemetry-instrumentation-long-task
[devDependencies-image]: https://status.david-dm.org/gh/open-telemetry/opentelemetry-js-contrib.svg?path=plugins%2Fweb%2Fopentelemetry-instrumentation-long-task&type=dev
[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib?path=plugins%2Fweb%2Fopentelemetry-instrumentation-long-task&type=dev
[npm-url]: https://www.npmjs.com/package/@opentelemetry/instrumentation-long-task
[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Finstrumentation-long-task.svg
[mdn-long-task]: https://developer.mozilla.org/en-US/docs/Web/API/Long_Tasks_API
[mdn-performance-long-task-timing]: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceLongTaskTiming
25 changes: 25 additions & 0 deletions plugins/web/opentelemetry-instrumentation-long-task/karma.conf.js
@@ -0,0 +1,25 @@
/*!
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const karmaWebpackConfig = require('../../../karma.webpack');
const karmaBaseConfig = require('../../../karma.base');

module.exports = (config) => {
config.set(Object.assign({}, karmaBaseConfig, {
frameworks: karmaBaseConfig.frameworks.concat(['jquery-1.8.3']),
webpack: karmaWebpackConfig,
}))
};
91 changes: 91 additions & 0 deletions plugins/web/opentelemetry-instrumentation-long-task/package.json
@@ -0,0 +1,91 @@
{
"name": "@opentelemetry/instrumentation-long-task",
"version": "0.27.0",
"description": "OpenTelemetry long task API automatic instrumentation package.",
"main": "build/src/index.js",
"module": "build/esm/index.js",
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js-contrib",
"scripts": {
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"clean": "tsc --build --clean tsconfig.json tsconfig.esm.json",
"codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"precompile": "tsc --version && lerna run version --scope @opentelemetry/instrumentation-user-interaction --include-filtered-dependencies",
"prewatch": "npm run precompile",
"version:update": "node ../../../scripts/version-update.js",
"compile": "npm run version:update && tsc --build tsconfig.json tsconfig.esm.json",
"prepare": "npm run compile",
"tdd": "karma start",
"test:browser": "nyc karma start --single-run",
"watch": "tsc --build --watch tsconfig.json tsconfig.esm.json"
},
"keywords": [
"opentelemetry",
"web",
"tracing",
"profiling",
"metrics",
"stats"
],
"author": "OpenTelemetry Authors",
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
},
"files": [
"build/esm/**/*.js",
"build/esm/**/*.map",
"build/esm/**/*.d.ts",
"build/src/**/*.js",
"build/src/**/*.map",
"build/src/**/*.d.ts",
"doc",
"LICENSE",
"README.md"
],
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@babel/core": "7.15.0",
"@opentelemetry/api": "1.0.2",
"@opentelemetry/sdk-trace-base": "1.0.1",
"@types/jquery": "3.5.6",
"@types/mocha": "7.0.2",
"@types/node": "14.17.9",
"@types/sinon": "10.0.2",
"@types/webpack-env": "1.16.2",
"babel-loader": "8.2.2",
"codecov": "3.8.3",
"gts": "3.1.0",
"istanbul-instrumenter-loader": "3.0.1",
"karma": "5.2.3",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jquery": "0.2.4",
"karma-mocha": "2.0.1",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "4.0.2",
"mocha": "7.2.0",
"nyc": "15.1.0",
"rimraf": "3.0.2",
"sinon": "11.1.2",
"ts-loader": "8.3.0",
"ts-mocha": "8.0.0",
"typescript": "4.3.5",
"webpack": "4.46.0",
"webpack-cli": "4.7.2",
"webpack-merge": "5.8.0",
"zone.js": "0.11.4"
},
"dependencies": {
"@opentelemetry/core": "^1.0.0",
"@opentelemetry/instrumentation": "^0.27.0",
"@opentelemetry/sdk-trace-web": "^1.0.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.0.2"
},
"sideEffects": false
}
17 changes: 17 additions & 0 deletions plugins/web/opentelemetry-instrumentation-long-task/src/index.ts
@@ -0,0 +1,17 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './instrumentation';
@@ -0,0 +1,135 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { hrTime } from '@opentelemetry/core';
import {
InstrumentationBase,
InstrumentationConfig,
} from '@opentelemetry/instrumentation';
import { VERSION } from './version';

// Currently missing in typescript DOM definitions
interface PerformanceLongTaskTiming extends PerformanceEntry {
attribution: TaskAttributionTiming[];
}

interface TaskAttributionTiming extends PerformanceEntry {
containerType: string;
containerSrc: string;
containerId: string;
containerName: string;
}

const LONGTASK_PERFORMANCE_TYPE = 'longtask';

export class LongTaskInstrumentation extends InstrumentationBase {
readonly component: string = 'long-task';
readonly version: string = VERSION;
moduleName = this.component;

private _observer?: PerformanceObserver;

/**
*
* @param config
*/
constructor(config: InstrumentationConfig = {}) {
super('@opentelemetry/instrumentation-long-task', VERSION, config);
}

init() {}

private isSupported() {
if (
typeof PerformanceObserver === 'undefined' ||
!PerformanceObserver.supportedEntryTypes
) {
return false;
}

return PerformanceObserver.supportedEntryTypes.includes(
LONGTASK_PERFORMANCE_TYPE
);
}

private _createSpanFromEntry(entry: PerformanceLongTaskTiming) {
const span = this.tracer.startSpan(LONGTASK_PERFORMANCE_TYPE, {
startTime: hrTime(entry.startTime),
});
span.setAttribute('component', this.component);
span.setAttribute('longtask.name', entry.name);
span.setAttribute('longtask.entry_type', entry.entryType);
span.setAttribute('longtask.duration', entry.duration);

if (Array.isArray(entry.attribution)) {
entry.attribution.forEach((attribution, index) => {
const prefix =
entry.attribution.length > 1
? `longtask.attribution[${index}]`
: 'longtask.attribution';
span.setAttribute(`${prefix}.name`, attribution.name);
span.setAttribute(`${prefix}.entry_type`, attribution.entryType);
span.setAttribute(`${prefix}.start_time`, attribution.startTime);
span.setAttribute(`${prefix}.duration`, attribution.duration);
span.setAttribute(
`${prefix}.container_type`,
attribution.containerType
);
span.setAttribute(`${prefix}.container_src`, attribution.containerSrc);
span.setAttribute(`${prefix}.container_id`, attribution.containerId);
span.setAttribute(
`${prefix}.container_name`,
attribution.containerName
);
});
}

span.end(hrTime(entry.startTime + entry.duration));
}

override enable() {
if (!this.isSupported()) {
this._diag.debug('Environment not supported');
return;
}

if (this._observer) {
// Already enabled
return;
}

this._observer = new PerformanceObserver(list => {
list
.getEntries()
.forEach(entry =>
this._createSpanFromEntry(entry as PerformanceLongTaskTiming)
);
});
this._observer.observe({
type: LONGTASK_PERFORMANCE_TYPE,
buffered: true,
});
}

override disable() {
if (!this._observer) {
return;
}

this._observer.disconnect();
this._observer = undefined;
}
}

0 comments on commit 56d332e

Please sign in to comment.