Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Long Tasks instrumentation #757

Merged
merged 8 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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,
}))
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"name": "@opentelemetry/instrumentation-long-task",
"version": "0.26.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.26.0",
"@opentelemetry/sdk-trace-web": "^1.0.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.0.2"
},
"sideEffects": false
}
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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);
Comment on lines +72 to +75
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume there is no spec for these attributes. Can we see if there is any guidance from the spec/TC on how we should handle custom or non-specified attributes? If these are ever specified in the future in a different format it could make us non-compliant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dyladan instrumentation are not stable yet so this shouldn't be considered a breaking change if in the future they are in the spec ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No but in general I think we should try to be consistent.


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;
}
}