Repository: electron/electron
Pull request: ci: upload stats about patches to DataDog

#### Description of Change

<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
-->

It would be neat to track stats about our patches over time, so we can keep an eye on it. This is a quick and dirty attempt at throwing the data into DataDog so we can graph it there.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd

#### Release Notes

Notes: none <!-- Please add a one-line description for app developers to read in the release notes, or 'none' if no notes relevant to app developers. Examples and help on special cases: https://github.com/electron/clerk/blob/main/README.md#examples -->


Changed files:
filename: .github/actions/checkout/action.yml
patch: @@ -136,6 +136,11 @@ runs:
     run: |
       echo "::remove-matcher owner=merge-conflict::"
       echo "::remove-matcher owner=patch-conflict::"
+  - name: Upload patches stats
+    if: ${{ inputs.target-platform == 'linux' && github.ref == 'refs/heads/main' }}
+    shell: bash
+    run: |
+      npx node src/electron/script/patches-stats.mjs --upload-stats || true
   # delete all .git directories under src/ except for
   # third_party/angle/ and third_party/dawn/ because of build time generation of files
   # gen/angle/commit.h depends on third_party/angle/.git/HEAD

filename: .github/workflows/build.yml
patch: @@ -131,6 +131,7 @@ jobs:
     env:
       CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
       CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
+      DD_API_KEY: ${{ secrets.DD_API_KEY }}
       GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
       PATCH_UP_APP_CREDS: ${{ secrets.PATCH_UP_APP_CREDS }}
     outputs:
@@ -144,6 +145,8 @@ jobs:
         ref: ${{ github.event.pull_request.head.sha }}
     - name: Checkout & Sync & Save
       uses: ./src/electron/.github/actions/checkout
+      with:
+        target-platform: linux
 
   checkout-windows:
     needs: setup

filename: script/patches-stats.mjs
patch: @@ -0,0 +1,87 @@
+import * as fs from 'node:fs/promises';
+import * as path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { parseArgs } from 'node:util';
+
+async function main () {
+  const { values: { 'upload-stats': uploadStats } } = parseArgs({
+    options: {
+      'upload-stats': {
+        type: 'boolean',
+        default: false
+      }
+    }
+  });
+
+  const config = JSON.parse(await fs.readFile(path.join(import.meta.dirname, '..', 'patches', 'config.json'), 'utf-8'));
+
+  let patchCount = 0;
+  let patchesLineCount = 0;
+
+  const root = path.join(import.meta.dirname, '..', '..', '..');
+
+  for (const target of config) {
+    const patchDirPath = path.join(root, target.patch_dir);
+    const patches = await fs.readFile(path.join(patchDirPath, '.patches'), 'utf-8');
+
+    for (const patch of patches.trim().split('\n')) {
+      patchCount++;
+      const contents = await fs.readFile(path.join(patchDirPath, patch), 'utf-8');
+      patchesLineCount += Array.from(contents.matchAll(/\n/g)).length;
+    }
+  }
+
+  console.log(`Total patches: ${patchCount}`);
+  console.log(`Total lines in patches: ${patchesLineCount}`);
+
+  if (uploadStats) {
+    if (!process.env.DD_API_KEY) {
+      throw new Error('DD_API_KEY is not set');
+    }
+
+    const timestamp = Math.round(new Date().getTime() / 1000);
+
+    await fetch('https://api.datadoghq.com/api/v2/series', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'DD-API-KEY': process.env.DD_API_KEY
+      },
+      body: JSON.stringify({
+        series: [
+          {
+            metric: 'electron.patches.count',
+            points: [
+              {
+                timestamp,
+                value: patchCount
+              }
+            ],
+            type: 1 // COUNT
+          },
+          {
+            metric: 'electron.patches.lineCount',
+            points: [
+              {
+                timestamp,
+                value: patchesLineCount
+              }
+            ],
+            type: 1 // COUNT
+          }
+        ]
+      })
+    });
+  }
+}
+
+if ((await fs.realpath(process.argv[1])) === fileURLToPath(import.meta.url)) {
+  main()
+    .then(() => {
+      process.exit(0);
+    })
+    .catch((err) => {
+      console.error(`ERROR: ${err.message}`);
+      process.exit(1);
+    });
+}