Skip to content
This repository was archived by the owner on Nov 19, 2025. It is now read-only.

Commit 7451b12

Browse files
authored
Merge pull request #11 from codecov/publish_to_npm
Publish to NPM Support
2 parents 45d6235 + 05a2c67 commit 7451b12

File tree

5 files changed

+116
-43
lines changed

5 files changed

+116
-43
lines changed

.github/workflows/node.js.yml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
33

44
name: Node.js CI
5-
6-
on:
7-
push:
8-
branches: [ main ]
9-
pull_request:
5+
on: [push, pull_request]
106

117
jobs:
128
build:
@@ -15,7 +11,7 @@ jobs:
1511

1612
strategy:
1713
matrix:
18-
node-version: [12.x, 14.x, 16.x]
14+
node-version: [15.x, 16.x]
1915
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2016

2117
steps:
@@ -32,5 +28,4 @@ jobs:
3228
with:
3329
token: ${{ secrets.CODECOV_TOKEN }}
3430
files: ./coverage/clover.xml
35-
flags: unittests
36-
verbose: true
31+
flags: unittests

.github/workflows/publish.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Publish to NPM
2+
on:
3+
release:
4+
types: [created]
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Checkout
10+
uses: actions/checkout@v2
11+
- name: Setup Node
12+
uses: actions/setup-node@v2
13+
with:
14+
node-version: '16.x'
15+
registry-url: 'https://registry.npmjs.org'
16+
- name: Install dependencies and build 🔧
17+
run: npm ci && npm run build --if-present
18+
- name: Publish package on NPM 📦
19+
run: npm publish --access public
20+
env:
21+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

README.md

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,98 @@
1-
# Critical paths with NODE_V8_COVERAGE
1+
# Node Codecov OpenTelementry
22

3-
The moment we've been waiting for! Node 15.1.0 has added v8.takeCoverage(). see
3+
This package is intended to support Codecov's [Impact Analysis](https://docs.codecov.com/docs/impact-analysis) feature.
4+
5+
Note that this packaged requires, at minimum, Node 15.1.0 due to the inclusion of v8.takeCoverage(). See
46
[https://nodejs.org/api/v8.html#v8_v8_takecoverage](https://nodejs.org/api/v8.html#v8_v8_takecoverage).
57

68
## Setup
79

810

911
Install dependencies:
1012

11-
`npm install`
13+
`npm install @codecov/node-codecov-opentelemetry`
1214

1315
Set environment variable for coverage export. You will not need to access this directory
1416
yourself, but the application will read coverage reports from this directory:
1517

1618
`export NODE_V8_COVERAGE=codecov_reports`
1719

18-
Run application
20+
An example application is included in this repository, it can be run as follows:
1921

2022
```
2123
node examples/app.js
2224
```
2325

24-
IMPORTANT: Be sure you're running Node JS 15.1 or above. Any version beneath this one
25-
will not have the interface to V8 that we need to collect traces.
26+
**IMPORTANT:** Be sure you're running Node JS 15.1 or above. Any version beneath this one
27+
will not have the interface to V8 required to collect traces.
28+
29+
## Basic Setup
30+
31+
The following code should be used in the startup of your application, typically this is `app.js`. For a basic express app, it would look as follows:
32+
33+
```js
34+
// Include Dependencies
35+
const { CodeCovOpenTelemetry } = require('@codecov/node-codecov-opentelemetry');
36+
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
37+
const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
38+
const { SpanKind } = require("@opentelemetry/api");
39+
40+
// Setup OpenTelemetry
41+
const sampleRate = 1;
42+
const untrackedExportRate = 1;
43+
const code = 'production::v0.0.1' //<environment>::<versionIdentifier>
44+
const provider = new NodeTracerProvider();
45+
provider.register();
46+
47+
// Setup Codecov OTEL
48+
const codecov = new CodeCovOpenTelemetry(
49+
{
50+
repositoryToken: "your-impact-analysis-token", //from repository settings page on Codecov.
51+
environment: "production", //or others as appropriate
52+
versionIdentifier: "v0.0.1", //semver, commit SHA, etc
53+
filters: {
54+
allowedSpanKinds: [SpanKind.SERVER],
55+
},
56+
codecovEndpoint: "https://api.codecov.io",
57+
sampleRate,
58+
untrackedExportRate,
59+
code
60+
}
61+
)
62+
63+
provider.addSpanProcessor(codecov.processor);
64+
provider.addSpanProcessor(new BatchSpanProcessor(codecov.exporter))
65+
66+
```
67+
Once initialized, your application can continue as expected:
2668

27-
## Things that could possibly improve in the long term
69+
```js
70+
//...example express setup
71+
const express = require('express');
72+
const port = 3000;
73+
const app = express();
2874

29-
1. Due to the nature of node coverage profiling (essentially calling `takeCoverage` again and again and using snapshots), there doesn't seem to be a pause/resume method on collecting coverage. This makes it so, even on logic we don't want to track coverage on, coverage tracking is still running. This can have a bad impact on performance.
30-
- Calling `stopCoverage` is not a possibility, because once `stopCoverage` is called, `takeCoverage` starts raising errors
31-
- It doesn't seem (or at least first experiments don't seem to show) this has a bad impact on performance. Probably because it's native. The imaect from this is way less than the impact of saving and reading data from disk
75+
app.get('/', (req, res) => {
76+
res.send('Hello World!');
77+
})
78+
79+
```
80+
81+
## Current Caveats and Limitations
82+
83+
### NodeJS and `takeCoverage`
84+
This package relies heavily on the `takeCoverage` and other supporting methods added to with Node 15.1.0. While these methods are generally useful and allow Impact Analysis to function properly, there are some caveats to consider:
85+
86+
1. Due to the nature of node coverage profiling (essentially calling `takeCoverage` again and again and using snapshots), coverage tracking cannot be paused, and may run in a way that poses an impact on performance.
87+
- On approach to address this problem, calling `stopCoverage` is not a possibility, because once `stopCoverage` is called, `takeCoverage` raises errors.
88+
- Initial experiments do not indicate a significant hit to performance, likely due to `takeCoverage()` _et al_ being native.
3289
2. There seems to be no way to avoid the disk-write in a naive way
33-
- If we can map `NODE_V8_COVERAGE` to memory, that could be more performant
34-
3. For problems 1. and 2. we might be able to go one level lower with the profiler. But it involved calling C code more directly, which I don't understand enough to do.
35-
3. Getting the saved filename is a bit error-prone. We can't know for sure what the filename will be because it's generated on the fly: https://github.com/nodejs/node/blob/c18ad4b01297548582a04000aae5ba7d862377f5/src/inspector_profiler.cc#L172 So there is the possibility that at some point we will use the wrong filename in some race condition.
36-
4. There seems to be a bug in node opentelem in that is calls spancontext as a function, but that is not a function. We have to solve it before people use this.
37-
- https://github.com/open-telemetry/opentelemetry-js/blob/610808d3b64b9f660f4dd640ad961b8c9f67be66/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts#L83
38-
- There was one more place. But I can't find it now
39-
5. The output from the profiler is in the format:
90+
- If `NODE_V8_COVERAGE` could be mapped to memory, that could be more performant solution.
91+
3. Getting the saved filename is a bit error-prone. We can't know for sure what the filename will be because it's generated on the fly. See: [https://github.com/nodejs/node/.../src/inspector_profiler.cc#L172](https://github.com/nodejs/node/blob/c18ad4b01297548582a04000aae5ba7d862377f5/src/inspector_profiler.cc#L172) So there is the possibility that at some point race conditions may occur, although this is not likely.
92+
4. There seems to be a bug in node opentelem in that calls `spancontext` as a function, but that is not a function. Before fielding this in a production context, opentelemetry-js will need a fix.
93+
- example of incorrect use: [https://github.com/open-telemetry/opentelemetry-js/.../src/export/BatchSpanProcessorBase.ts#L83](https://github.com/open-telemetry/opentelemetry-js/blob/610808d3b64b9f660f4dd640ad961b8c9f67be66/packages/opentelemetry-sdk-trace-base/src/export/BatchSpanProcessorBase.ts#L83)
94+
95+
5. There are some minor concerns with block coverage versus line coverage. For example, the output from the profiler is in the format:
4096
```
4197
// ...
4298
{
@@ -58,11 +114,14 @@ will not have the interface to V8 that we need to collect traces.
58114
}
59115
// ...
60116
```
61-
which shows byte ranges (1062 to 1107 in this case). This means that coverage is more on the statement block level, rather than line coverage. To convert to line coverage, we need to make some smart analysis to tell which lines are being executed. Otherwise the best we can do is say: bytes A to B involves lines C to D, therefore all lines from C to D are covered (and some easy removals like empty lines). But this is not really exact and we might need some static analysis to tell which lines are actually lines. For now we are doing the naive thing, in the hopes this is enough for this version.
62-
6. We are assuming that the "byte intervals" that show up in node coverage are presented in pre-order when looking at the interval tree. If that is correct, we don't need to reorder the intervals. If that is not, we need to reorder to avoid one interval wrongly overwriting its subintervals data.
63-
- We are reordering it, but we are still assuming they are tree intervals and that there will be no unusual overlaps (as in, two intervals that overlap but are not contained one inside another)
64-
7. Due to the nature of async js, opentelemetry tracks the request from the moment it comes until the moment it is responded. So for example, on:
65-
```
117+
118+
which shows byte ranges (1062 to 1107 in this case). This means that coverage is on the statement block level, rather than line coverage. To compensate for this discrepancy, for now, this package assumes that if bytes A to B involve lines C to D, then all lines from C to D are covered.
119+
6. This package assumes that the "byte intervals" that show up in node coverage are presented in pre-order when looking at the interval tree. This package makes no assumption that byte intervals are presented in pre-order, and thus will reorder if needed, However, the package still assumes they are tree intervals and that there will be no unusual overlaps (as in, two intervals that overlap but are not contained one inside another).
120+
121+
### OpenTelemetry Caveats
122+
1. Due to the nature of async js, opentelemetry tracks the request from the moment it is received until the moment of response. So for example, on:
123+
124+
```js
66125
app.get('/hello', (req, res) => {
67126
console.log("WE ARE INSIDE THE REQUEST")
68127
res.send('SPECIAL Hello ' + req.query.name + req.query.value);
@@ -75,10 +134,8 @@ app.get('/hello', (req, res) => {
75134
}
76135
```
77136
78-
The opentelemetry will execute `onEnd` right after `res.send` happens. Which means that it won't wait for the extra logic to run. I don't know if the extra logic post-response is a common practice at Node, but we have no way of checking if coverage is run there, even though it does run.
137+
Opentelemetry will execute `onEnd` right after `res.send` happens. Which means that it won't wait for the extra logic to run.
79138
80-
8. Connected to the above point, but a different problem: there is the fact that js coverage output doesn't seem to split a 'stataments block' into two when needed. The idea is that two consecutive statements, unless separated by an if/while/return or whatnot, are always either both be executed or neither (which makes sense on its own)
81-
- So, still on the above example, lines 69 (`let a ...;`) and 70 (`let b = ...;`) are in the same statement block as line 67 (`console.log("WE...")`). Due to the async nature of js, coverage stopped tracking before they were executed, so they should not show up on the coverage result. But they do, because since line 67 was executed, and they are part of the same statement block, it doesn't make sense for them to not have been executed.
82-
- One might think "Great, this undoes the problem from item 7.". But the issue is inside the `if`, for example. Line 72 (`console.log("It's higher...")`) also clearly runs on some cases (where a > 10), but is always considered not covered on the reports, because it is on a separate statement block and happens after a res.send.
83-
- It's almost like the coverage tooling static analysis that produces the execution tree is not considering the dynamic possibilities of the code.
84-
9. It's not clear to me that the callbacks on export are working. We set it seemingly right, but it's hard to tell if the tool expects something else.
139+
2. JS coverage output doesn't seem to split a 'stataments block' into two when needed. The idea is that two consecutive statements, unless separated by an if/while/return/etc, are always either both executed or neither.
140+
- So, still on the above example, lines 68 (`let a ...;`) and 69 (`let b = ...;`) are in the same statement block as line 66 (`console.log("WE...")`). Due to the async nature of js, coverage stopped tracking before they were executed, so they should not show up on the coverage result. But they do, because since line 66 was executed, and they are part of the same statement block, it doesn't make sense for them to not have been executed.
141+
- While this is generally preferred, problems do arise in some cases. Consider the `if` statement on line 70, for example. Line 71 (`console.log("It's higher...")`) also clearly runs on some cases (where `a > 10`), but is always considered not covered on the reports, because it is on a separate statement block and happens after a `res.send`.

examples/app.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ const { SpanKind } = require("@opentelemetry/api");
66
// OTEL setup logic
77
const sampleRate = 1;
88
const untrackedExportRate = 1;
9-
const code = 'production::v2'
9+
const code = 'production::v0.0.1' //<environment>::<versionIdentifier>
1010

1111
const provider = new NodeTracerProvider();
1212
provider.register();
1313

1414
const codecov = new CodeCovOpenTelemetry(
1515
{
16-
repositoryToken: "c9efdfba42b1209a855071a32672c9017989c01e", // local key, no security risk
17-
environment: "production",
18-
versionIdentifier: "v2",
16+
repositoryToken: "your-impact-analysis-token", //from repository settings page on Codecov.
17+
environment: "production", //or others as appropriate
18+
versionIdentifier: "v0.0.1", //semver
1919
filters: {
2020
allowedSpanKinds: [SpanKind.SERVER],
2121
},
22-
codecovEndpoint: "localhost",
22+
codecovEndpoint: "https://api.codecov.io",
2323
sampleRate,
2424
untrackedExportRate,
2525
code

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "@codecov/opentelem-node",
3-
"version": "0.1.0",
2+
"name": "@codecov/node-codecov-opentelemetry",
3+
"version": "0.0.5",
44
"license": "MIT",
55
"description": "",
66
"main": "lib/runtime-insights.js",

0 commit comments

Comments
 (0)