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

[telemetry] Rewrite Telemetry module to be more universal #90

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7ab44ac
Move endPoint option into telemetry
yoannmoinet Jul 19, 2024
9db4fba
Update outputDir for rollup and vite
yoannmoinet Jul 19, 2024
2d2c603
Add more to the global context
yoannmoinet Jul 22, 2024
a8dc34f
Fix markdown links with <img />
yoannmoinet Jul 24, 2024
3c7755b
Rename config into rawConfig
yoannmoinet Jul 24, 2024
0866fc8
Update how we pass rollup config to vite
yoannmoinet Jul 25, 2024
bd15891
Export the version with the plugins
yoannmoinet Aug 8, 2024
5fa87da
Revamp global context with more shared data
yoannmoinet Aug 8, 2024
e459283
Minor tweaks in tests platform
yoannmoinet Aug 8, 2024
9e897ac
Streamline telemetry plugin and introduce universal plugin
yoannmoinet Aug 12, 2024
5b01490
More universal telemetry for esbuild
yoannmoinet Aug 13, 2024
52c1258
Clean esbuild metrics
yoannmoinet Aug 13, 2024
82ef51e
Universalize webpack telemetry
yoannmoinet Aug 19, 2024
266cc21
Small fixes for esbuild
yoannmoinet Aug 19, 2024
dff0894
Split out a build-report plugin
yoannmoinet Aug 19, 2024
61fd640
Tweaks for testing
yoannmoinet Aug 19, 2024
f5e2f1e
Fix sourcemaps reports
yoannmoinet Aug 19, 2024
a17902b
Start writing tests for build-report plugin
yoannmoinet Aug 20, 2024
473752a
Better setup fixture project
yoannmoinet Aug 21, 2024
e278066
Configure bundlers to have similar build's output
yoannmoinet Aug 21, 2024
4d953ca
Small fixes following more complex tests
yoannmoinet Aug 21, 2024
20e0477
More tests and uniformisation of bundlers
yoannmoinet Aug 21, 2024
4d4705b
Finalize and clean for tests of build-report
yoannmoinet Aug 23, 2024
e841d80
Clean some more
yoannmoinet Aug 23, 2024
4e485c7
Some cleaning and integrity
yoannmoinet Aug 23, 2024
2c44201
Fix git-plugin test
yoannmoinet Aug 23, 2024
ae93a06
Fix some tests
yoannmoinet Aug 26, 2024
9ad48b1
Missing integrity
yoannmoinet Aug 26, 2024
1578696
Update some types
yoannmoinet Aug 26, 2024
304a57d
Add dependencies and dependents to inputs
yoannmoinet Aug 27, 2024
b5841b5
WIP rollup dependents and dependencies
yoannmoinet Aug 29, 2024
a904b5e
Complete rollup dependencies and dependents
yoannmoinet Aug 29, 2024
5131a77
Complete dependents and dependencies
yoannmoinet Aug 29, 2024
73a9e10
Change what we report in dependencies and dependents
yoannmoinet Sep 3, 2024
4a339f7
Update esbuild report dependencies dependents
yoannmoinet Sep 3, 2024
d355f67
Update rollup report dependencies dependents
yoannmoinet Sep 3, 2024
b586afd
Update webpack report dependencies dependents
yoannmoinet Sep 4, 2024
97628c1
Better warn logging and indexing
yoannmoinet Sep 4, 2024
39bd316
Add --bundlers flag for tests
yoannmoinet Sep 4, 2024
8896e1a
Update metrics reported
yoannmoinet Sep 4, 2024
82b08c7
Cleaning
yoannmoinet Sep 4, 2024
ef4de0c
Remove dependencies.json file
yoannmoinet Sep 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ A set of plugins to interact with Datadog directly from your builds.

<!-- #toc -->
- [Bundler Plugins](#bundler-plugins)
- [ESBuild](#esbuild)
- [Rollup](#rollup)
- [Vite](#vite)
- [Webpack](#webpack)
- [ESBuild](#-esbuild)
- [Rollup](#-rollup)
- [Vite](#-vite)
- [Webpack](#-webpack)
- [Features](#features)
- [RUM](#rum)
- [Telemetry](#telemetry)
- [RUM](#rum----)
- [Telemetry](#telemetry--)
- [Configuration](#configuration)
- [`auth.apiKey`](#authapikey)
- [`auth.endPoint`](#authendpoint)
- [`logLevel`](#loglevel)
- [Contributing](#contributing)
- [License](#license)
Expand Down Expand Up @@ -223,6 +222,7 @@ datadogWebpackPlugin({
datadogWebpackPlugin({
telemetry?: {
disabled?: boolean,
endPoint?: string,
output?: boolean
| string
| {
Expand Down Expand Up @@ -251,7 +251,6 @@ datadogWebpackPlugin({
{
auth?: {
apiKey?: string;
endPoint?: string;
};
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'none';
rum?: {
Expand All @@ -267,6 +266,7 @@ datadogWebpackPlugin({
};
telemetry?: {
disabled?: boolean;
endPoint?: string;
output?: boolean
| string
| {
Expand All @@ -292,12 +292,6 @@ datadogWebpackPlugin({

In order to interact with Datadog, you have to use [your own API Key](https://app.datadoghq.com/organization-settings/api-keys).

### `auth.endPoint`

> default: `"app.datadoghq.com"`

To which endpoint will the metrics be sent.

### `logLevel`

> default: `'warn'`
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"repository": "git://github.com/DataDog/build-plugins.git",
"workspaces": [
"packages/*",
"packages/plugins/*"
"packages/plugins/*",
"packages/tests/src/fixtures/project"
],
"volta": {
"node": "18.19.0",
Expand Down
279 changes: 279 additions & 0 deletions packages/core/src/plugins/build-report/esbuild.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import path from 'path';
import type { UnpluginOptions } from 'unplugin';

import type { Logger } from '../../log';
import type { Entry, GlobalContext, Input, Output } from '../../types';

import { cleanName, getType } from './helpers';

// Re-index metafile data for easier access.
const reIndexMeta = <T>(obj: Record<string, T>, cwd: string) =>
Object.fromEntries(
Object.entries(obj).map(([key, value]) => {
const newKey = path.join(cwd, key);
return [newKey, value];
}),
);

export const getEsbuildPlugin = (
context: GlobalContext,
log: Logger,
): UnpluginOptions['esbuild'] => {
return {
setup(build) {
const cwd = context.cwd;

// Store entry names based on the configuration.
const entrypoints = build.initialOptions.entryPoints;
const entryNames = new Map();
if (Array.isArray(entrypoints)) {
// We don't have an indexed object as entry, so we can't get an entry name from it.
for (const entry of entrypoints) {
const fullPath = entry && typeof entry === 'object' ? entry.in : entry;
const realEntry = cleanName(context, fullPath);
entryNames.set(realEntry, realEntry);
}
} else if (entrypoints) {
const entryList = entrypoints ? Object.entries(entrypoints) : [];
for (const [entryName, entryPath] of entryList) {
entryNames.set(cleanName(context, entryPath), entryName);
}
}

build.onEnd((result) => {
context.build.errors = result.errors.map((err) => err.text);
context.build.warnings = result.warnings.map((err) => err.text);

const warn = (warning: string) => {
context.build.warnings.push(warning);
log(warning, 'warn');
};

if (!result.metafile) {
warn('Missing metafile from build result.');
return;
}

const inputs: Input[] = [];
const outputs: Output[] = [];
const tempEntryFiles: Entry[] = [];
const tempSourcemaps: Output[] = [];
const entries: Entry[] = [];

const reportInputsIndexed: Record<string, Input> = {};
const reportOutputsIndexed: Record<string, Output> = {};

const metaInputsIndexed = reIndexMeta(result.metafile.inputs, cwd);
const metaOutputsIndexed = reIndexMeta(result.metafile.outputs, cwd);

// Loop through inputs.
for (const [filename, input] of Object.entries(result.metafile.inputs)) {
const filepath = path.join(cwd, filename);
const file: Input = {
name: cleanName(context, filename),
filepath,
dependents: [],
dependencies: [],
size: input.bytes,
type: getType(filename),
};
reportInputsIndexed[filepath] = file;
inputs.push(file);
}

// Loop through outputs.
for (const [filename, output] of Object.entries(result.metafile.outputs)) {
const fullPath = path.join(cwd, filename);
const cleanedName = cleanName(context, fullPath);
// Get inputs of this output.
const inputFiles: Input[] = [];
for (const inputName of Object.keys(output.inputs)) {
const inputFound = reportInputsIndexed[path.join(cwd, inputName)];
if (!inputFound) {
warn(`Input ${inputName} not found for output ${cleanedName}`);
continue;
}

inputFiles.push(inputFound);
}

// When splitting, esbuild creates an empty entryPoint wrapper for the chunk.
// It has no inputs, but still relates to its entryPoint.
if (output.entryPoint && !inputFiles.length) {
const inputFound = reportInputsIndexed[path.join(cwd, output.entryPoint!)];
if (!inputFound) {
warn(`Input ${output.entryPoint} not found for output ${cleanedName}`);
continue;
}
inputFiles.push(inputFound);
}

const file: Output = {
name: cleanedName,
filepath: fullPath,
inputs: inputFiles,
size: output.bytes,
type: getType(fullPath),
};

reportOutputsIndexed[fullPath] = file;

// Store sourcemaps for later filling.
if (file.type === 'map') {
tempSourcemaps.push(file);
}

outputs.push(file);

if (!output.entryPoint) {
continue;
}

const inputFile = reportInputsIndexed[path.join(cwd, output.entryPoint!)];

if (inputFile) {
// In the case of "splitting: true", all the files are considered entries to esbuild.
// Not to us.
// Verify we have listed it as an entry earlier.
if (!entryNames.get(inputFile.name)) {
continue;
}

const entry = {
...file,
name: entryNames.get(inputFile.name) || inputFile.name,
outputs: [file],
size: file.size,
};

tempEntryFiles.push(entry);
}
}

// Loop through sourcemaps.
for (const sourcemap of tempSourcemaps) {
const outputFilepath = sourcemap.filepath.replace(/\.map$/, '');
const foundOutput = reportOutputsIndexed[outputFilepath];

if (!foundOutput) {
warn(`Could not find output for sourcemap ${sourcemap.name}`);
continue;
}

sourcemap.inputs.push(foundOutput);
}

// Build our references for the entries.
const references = {
inputs: {
report: reportInputsIndexed,
meta: metaInputsIndexed,
},
outputs: {
report: reportOutputsIndexed,
meta: metaOutputsIndexed,
},
};

// Go through all imports.
const getAllImports = <T extends Input | Output>(
filePath: string,
ref: typeof references.inputs | typeof references.outputs,
allImports: Record<string, T> = {},
): Record<string, T> => {
const file = ref.report[filePath];
if (!file) {
warn(`Could not find report's ${filePath}`);
return allImports;
}

// Check if we already have processed it.
if (allImports[filePath]) {
return allImports;
}

allImports[file.filepath] = file as T;

const metaFile = ref.meta[filePath];
if (!metaFile) {
warn(`Could not find metafile's ${filePath}`);
return allImports;
}

// If there are no imports, we can return what we have.
if (!metaFile.imports || !metaFile.imports.length) {
return allImports;
}

for (const imported of metaFile.imports) {
const importPath = path.join(cwd, imported.path);
// Look for the other inputs.
getAllImports<T>(importPath, ref, allImports);
}

return allImports;
};

// Loop through entries.
for (const entryFile of tempEntryFiles) {
const entryInputs: Record<string, Input> = {};
const entryOutputs: Record<string, Output> = {};

// Do inputs for this entry.
for (const input of entryFile.inputs) {
getAllImports<Input>(input.filepath, references.inputs, entryInputs);
}

// Do outputs for this entry.
for (const outputFile of entryFile.outputs) {
getAllImports<Output>(
outputFile.filepath,
references.outputs,
entryOutputs,
);
}

entryFile.inputs = Object.values(entryInputs);
entryFile.outputs = Object.values(entryOutputs);
entryFile.size = entryFile.outputs.reduce(
(acc, output) => acc + output.size,
0,
);

entries.push(entryFile);
}

// Loop through all inputs to aggregate dependencies and dependents.
for (const input of inputs) {
const metaFile = references.inputs.meta[input.filepath];
if (!metaFile) {
warn(`Could not find metafile's ${input.name}`);
continue;
}

for (const dependency of metaFile.imports) {
const dependencyPath = path.join(cwd, dependency.path);
const dependencyFile = references.inputs.report[dependencyPath];

if (!dependencyFile) {
warn(`Could not find input file of ${dependency.path}`);
continue;
}

input.dependencies.push(dependencyFile);
// Add itself to the dependency's dependents.
dependencyFile.dependents.push(input);
}
}

context.build.outputs = outputs;
context.build.inputs = inputs;
context.build.entries = entries;
});
},
};
};
Loading
Loading