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

✨[RUM-3798] Report the cpu impact as a pr comment #2702

Merged
merged 21 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
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
12 changes: 11 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,17 @@ build-and-lint:
- yarn build
- yarn lint
- node scripts/check-packages.js
- node scripts/report-bundle-size/index.js

test-performance:
extends:
- .base-configuration
- .test-allowed-branches
interruptible: true
script:
- yarn
- yarn build:bundle
- node ./scripts/deploy/deploy.js staging pull-request pull-request
RomanGaignault marked this conversation as resolved.
Show resolved Hide resolved
- node scripts/performance/index.js

build-bundle:
extends:
Expand Down
16 changes: 12 additions & 4 deletions scripts/deploy/deploy.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
'use strict'

const { printLog, runMain } = require('../lib/execution-utils')
const { fetchPR, LOCAL_BRANCH } = require('../lib/git-utils')
const { command } = require('../lib/command')

const {
buildRootUploadPath,
buildDatacenterUploadPath,
buildBundleFolder,
buildBundleFileName,
buildPullRequestUploadPath,
packages,
} = require('./lib/deployment-utils')

Expand All @@ -27,20 +30,23 @@ const AWS_CONFIG = {
/**
* Deploy SDK files to CDN
* Usage:
* node deploy.js staging|prod staging|canary|vXXX root,us1,eu1,...
* node deploy.js staging|prod staging|canary|pull-request|vXXX root,pull-request,us1,eu1,...
*/
const env = process.argv[2]
const version = process.argv[3]
const uploadPathTypes = process.argv[4].split(',')

runMain(() => {
runMain(async () => {
const awsConfig = AWS_CONFIG[env]
let cloudfrontPathsToInvalidate = []
for (const { packageName } of packages) {
const bundleFolder = buildBundleFolder(packageName)
for (const uploadPathType of uploadPathTypes) {
let uploadPath
if (uploadPathType === 'root') {
if (uploadPathType === 'pull-request') {
const PR_NUMBER = (await fetchPR(LOCAL_BRANCH)).number
uploadPath = buildPullRequestUploadPath(packageName, PR_NUMBER)
} else if (uploadPathType === 'root') {
uploadPath = buildRootUploadPath(packageName, version)
} else {
uploadPath = buildDatacenterUploadPath(uploadPathType, packageName, version)
Expand All @@ -58,7 +64,9 @@ function uploadToS3(awsConfig, bundlePath, uploadPath) {
const accessToS3 = generateEnvironmentForRole(awsConfig.accountId, 'build-stable-browser-agent-artifacts-s3-write')

const browserCache =
version === 'staging' || version === 'canary' ? 15 * ONE_MINUTE_IN_SECOND : 4 * ONE_HOUR_IN_SECOND
version === 'staging' || version === 'canary' || version === 'pull-request'
? 15 * ONE_MINUTE_IN_SECOND
: 4 * ONE_HOUR_IN_SECOND
const cacheControl = `max-age=${browserCache}, s-maxage=60`

printLog(`Upload ${bundlePath} to s3://${awsConfig.bucketName}/${uploadPath}`)
Expand Down
5 changes: 5 additions & 0 deletions scripts/deploy/lib/deployment-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const buildDatacenterUploadPath = (datacenter, packageName, version, extension =
// ex: datadog-rum.js
const buildBundleFileName = (packageName, extension = 'js') => `datadog-${packageName}.${extension}`

// ex: pull-request/2781/datadog-rum.js
function buildPullRequestUploadPath(packageName, version, extension = 'js') {
return `pull-request/${version}/datadog-${packageName}.${extension}`
}
// ex: packages/rum/bundle
const buildBundleFolder = (packageName) => `packages/${packageName}/bundle`

Expand All @@ -23,4 +27,5 @@ module.exports = {
buildDatacenterUploadPath,
buildBundleFileName,
buildBundleFolder,
buildPullRequestUploadPath,
}
38 changes: 37 additions & 1 deletion scripts/lib/git-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,38 @@ const os = require('os')
const fs = require('fs')

const { command } = require('../lib/command')
const { getGithubDeployKey } = require('./secrets')
const { getGithubDeployKey, getGithubAccessToken } = require('./secrets')
const { fetchHandlingError } = require('./execution-utils')

const GITHUB_TOKEN = getGithubAccessToken()

async function fetchPR(localBranch) {
const response = await fetchHandlingError(
`https://api.github.com/repos/DataDog/browser-sdk/pulls?head=DataDog:${localBranch}`,
{
method: 'GET',
headers: {
Authorization: `token ${GITHUB_TOKEN}`,
},
}
)
const pr = response.body ? await response.json() : null
if (pr && pr.length > 1) {
throw new Error('Multiple pull requests found for the branch')
}
return pr ? pr[0] : null
}

function getLastCommonCommit(baseBranch) {
try {
command`git fetch --depth=100 origin ${baseBranch}`.run()
const commandOutput = command`git merge-base origin/${baseBranch} HEAD`.run()
// SHA commit is truncated to 8 characters as bundle sizes commit are exported in short format to logs for convenience and readability.
return commandOutput.trim().substring(0, 8)
} catch (error) {
throw new Error('Failed to get last common commit', { cause: error })
}
}

function initGitConfig(repository) {
const homedir = os.homedir()
Expand All @@ -21,4 +52,9 @@ function initGitConfig(repository) {

module.exports = {
initGitConfig,
fetchPR,
getLastCommonCommit,
BASE_BRANCH: process.env.MAIN_BRANCH,
LOCAL_BRANCH: process.env.CI_COMMIT_REF_NAME,
GITHUB_TOKEN,
}
29 changes: 29 additions & 0 deletions scripts/performance/bundle-size/compute-bundle-size.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const path = require('path')
const fs = require('fs')

const rumPath = path.join(__dirname, '../../../packages/rum/bundle/datadog-rum.js')
const logsPath = path.join(__dirname, '../../../packages/logs/bundle/datadog-logs.js')
const rumSlimPath = path.join(__dirname, '../../../packages/rum-slim/bundle/datadog-rum-slim.js')
const workerPath = path.join(__dirname, '../../../packages/worker/bundle/worker.js')

function getBundleSize(pathBundle) {
try {
const file = fs.statSync(pathBundle)
return file.size
} catch (error) {
throw new Error('Failed to get bundle size', { cause: error })
}
}

function calculateBundleSizes() {
return {
rum: getBundleSize(rumPath),
logs: getBundleSize(logsPath),
rum_slim: getBundleSize(rumSlimPath),
worker: getBundleSize(workerPath),
}
}

module.exports = {
calculateBundleSizes,
}
64 changes: 64 additions & 0 deletions scripts/performance/cpu-performance/compute-cpu-performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const { fetchHandlingError } = require('../../lib/execution-utils')
const { getOrg2ApiKey, getOrg2AppKey } = require('../../lib/secrets')
const { timeout } = require('../../lib/execution-utils')
const { fetchPR, LOCAL_BRANCH } = require('../../lib/git-utils')
const { LOCAL_COMMIT_SHA } = require('../report-as-a-pr-comment')
const API_KEY = getOrg2ApiKey()
const APP_KEY = getOrg2AppKey()
const TIMEOUT_IN_MS = 10000
const TEST_PUBLIC_ID = 'vcg-7rk-5av'
const RETRIES_NUMBER = 6

async function computeCpuPerformance() {
const prNumber = (await fetchPR(LOCAL_BRANCH)).number
const resultId = await triggerSyntheticsTest(prNumber, LOCAL_COMMIT_SHA)
await waitForSyntheticsTestToFinish(resultId, RETRIES_NUMBER)
}

async function triggerSyntheticsTest(prNumber, commitId) {
const body = {
tests: [
{
public_id: `${TEST_PUBLIC_ID}`,
startUrl: `https://datadoghq.dev/browser-sdk-test-playground/performance/?prNumber=${prNumber}&commitId=${commitId}`,
},
],
}
const url = 'https://api.datadoghq.com/api/v1/synthetics/tests/trigger/ci'
const response = await fetchHandlingError(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'DD-API-KEY': API_KEY,
'DD-APPLICATION-KEY': APP_KEY,
},
body: JSON.stringify(body),
})
const data = await response.json()
return data.results[0].result_id
}

async function waitForSyntheticsTestToFinish(resultId, RETRIES_NUMBER) {
const url = `https://api.datadoghq.com/api/v1/synthetics/tests/${TEST_PUBLIC_ID}/results/${resultId}`
for (let i = 0; i < RETRIES_NUMBER; i++) {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'DD-API-KEY': API_KEY,
'DD-APPLICATION-KEY': APP_KEY,
},
})
const data = await response.json()
if (data.length !== 0 && data.status === 0) {
await timeout(TIMEOUT_IN_MS) // Wait for logs ingestion
return
}
await timeout(TIMEOUT_IN_MS)
}
throw new Error('Synthetics test did not finish within the specified number of retries')
}

module.exports = {
computeCpuPerformance,
}
12 changes: 12 additions & 0 deletions scripts/performance/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { runMain } = require('../lib/execution-utils')
const { reportAsPrComment } = require('./report-as-a-pr-comment')
const { reportToDatadog } = require('./report-to-datadog')
const { calculateBundleSizes } = require('./bundle-size/compute-bundle-size')
const { computeCpuPerformance } = require('./cpu-performance/compute-cpu-performance')

runMain(async () => {
const localBundleSizes = calculateBundleSizes()
await computeCpuPerformance()
await reportToDatadog(localBundleSizes)
await reportAsPrComment(localBundleSizes)
})
43 changes: 43 additions & 0 deletions scripts/performance/query-performance-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { getOrg2ApiKey, getOrg2AppKey } = require('../lib/secrets')
const { fetchHandlingError } = require('../lib/execution-utils')
const ONE_DAY_IN_SECOND = 24 * 60 * 60

function fetchMetrics(type, names, commitId) {
return Promise.all(names.map((name) => fetchMetric(type, name, commitId)))
}

async function fetchMetric(type, name, commitId) {
const now = Math.floor(Date.now() / 1000)
const date = now - 30 * ONE_DAY_IN_SECOND
let query = ''

if (type === 'bundle') {
query = `avg:bundle_sizes.${name}{commit:${commitId}}&from=${date}&to=${now}`
} else if (type === 'cpu') {
query = `avg:cpu.sdk.${name}.performance.average{commitid:${commitId}}&from=${date}&to=${now}`
}

const response = await fetchHandlingError(`https://api.datadoghq.com/api/v1/query?query=${query}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'DD-API-KEY': getOrg2ApiKey(),
'DD-APPLICATION-KEY': getOrg2AppKey(),
},
})
const data = await response.json()
if (data.series && data.series.length > 0 && data.series[0].pointlist && data.series[0].pointlist.length > 0) {
return {
name,
value: data.series[0].pointlist[0][1],
}
}
return {
name,
value: null,
}
}

module.exports = {
fetchMetrics,
RomanGaignault marked this conversation as resolved.
Show resolved Hide resolved
}
Loading