current commit
260820.7 · build,win: add PGO workload scripts
'use strict';

// PGO Training Script: File System Operations
//
// Exercises the most common fs operations in Node.js applications:
// - readFile/writeFile (config loading, template rendering, static file serving)
// - stat/access (file existence checks, middleware, caching)
// - readdir (directory listings, file discovery, build tools)
// - read/write streams (log file appending, file upload/download)
// - mkdir/rmdir (temp directories, build artifacts)
// - path operations (resolve, join, parse — used constantly)
// - watch (file watchers in dev tools, though we only test creation here)
//
// This exercises: libuv filesystem operations, thread pool (async fs),
// Buffer allocation for file data, string encoding, path module.

const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const os = require('os');
const crypto = require('crypto');

const DURATION_MS = parseInt(process.env.PGO_TRAINING_DURATION, 10) || 15_000;

// Create a unique temp directory for our operations
const TEMP_DIR = path.join(
  os.tmpdir(),
  `node-pgo-fs-${process.pid}-${Date.now()}`,
);

// Test data
const MEDIUM_CONTENT = Array.from(
  { length: 100 },
  (_, i) => `Line ${i}: ${crypto.randomBytes(40).toString('hex')}`,
).join('\n');
const LARGE_CONTENT = crypto.randomBytes(256 * 1024).toString('base64');
const JSON_CONFIG = JSON.stringify(
  {
    database: {
      host: 'localhost',
      port: 5432,
      name: 'myapp',
      pool: { min: 2, max: 10 },
    },
    redis: { host: 'localhost', port: 6379, db: 0 },
    server: { port: 3000, host: '0.0.0.0', cors: { origin: '*' } },
    logging: { level: 'info', format: 'json', outputs: ['stdout', 'file'] },
    features: { darkMode: true, betaFeatures: false, maxUploadSize: 10485760 },
  },
  null,
  2,
);

function setup() {
  fs.mkdirSync(TEMP_DIR, { recursive: true });
  fs.mkdirSync(path.join(TEMP_DIR, 'subdir', 'nested'), { recursive: true });
  fs.mkdirSync(path.join(TEMP_DIR, 'logs'), { recursive: true });
  fs.mkdirSync(path.join(TEMP_DIR, 'uploads'), { recursive: true });
  fs.mkdirSync(path.join(TEMP_DIR, 'cache'), { recursive: true });

  // Pre-create files for reading
  for (let i = 0; i < 50; i++) {
    fs.writeFileSync(
      path.join(TEMP_DIR, `file-${i}.txt`),
      `Content of file ${i}\n`.repeat(10),
    );
  }
  for (let i = 0; i < 10; i++) {
    fs.writeFileSync(
      path.join(TEMP_DIR, 'subdir', `sub-${i}.json`),
      JSON.stringify({ id: i, data: `value-${i}` }),
    );
  }
  fs.writeFileSync(path.join(TEMP_DIR, 'config.json'), JSON_CONFIG);
  fs.writeFileSync(path.join(TEMP_DIR, 'large.dat'), LARGE_CONTENT);
  fs.writeFileSync(path.join(TEMP_DIR, 'medium.txt'), MEDIUM_CONTENT);
}

function cleanup() {
  try {
    fs.rmSync(TEMP_DIR, { recursive: true, force: true });
  } catch {
    // Best effort cleanup
  }
}

// Workload 1: readFile + writeFile (async, the dominant pattern)
async function workloadReadWriteAsync(iterations) {
  let ops = 0;

  for (let i = 0; i < iterations; i++) {
    // Read config file (very common — Express/Fastify load configs at startup)
    await fsp.readFile(path.join(TEMP_DIR, 'config.json'), 'utf8');
    ops++;

    // Read and parse JSON config
    const configStr = await fsp.readFile(
      path.join(TEMP_DIR, 'config.json'),
      'utf8',
    );
    JSON.parse(configStr);
    ops++;

    // Read binary file (static file serving)
    await fsp.readFile(path.join(TEMP_DIR, `file-${i % 50}.txt`));
    ops++;

    // Write new file (log rotation, temp files, uploads)
    const tempFile = path.join(TEMP_DIR, 'uploads', `upload-${i}.tmp`);
    await fsp.writeFile(
      tempFile,
      `Upload content ${i}: ${crypto.randomBytes(100).toString('hex')}`,
    );
    ops++;

    // Read back
    await fsp.readFile(tempFile, 'utf8');
    ops++;

    // Overwrite (atomic-ish update pattern)
    await fsp.writeFile(tempFile, `Updated content ${i}`);
    ops++;

    // Read large file (less frequently)
    if (i % 10 === 0) {
      await fsp.readFile(path.join(TEMP_DIR, 'large.dat'));
      ops++;
    }
  }
  return ops;
}

// Workload 2: readFileSync + writeFileSync (startup, require.resolve)
function workloadReadWriteSync(iterations) {
  let ops = 0;

  for (let i = 0; i < iterations; i++) {
    // Sync read (very common in require() for package.json, .json configs)
    fs.readFileSync(path.join(TEMP_DIR, 'config.json'), 'utf8');
    ops++;

    // Sync read various files
    fs.readFileSync(path.join(TEMP_DIR, `file-${i % 50}.txt`), 'utf8');
    ops++;

    // Sync JSON file read/parse (package.json pattern)
    for (let j = 0; j < 10; j++) {
      const content = fs.readFileSync(
        path.join(TEMP_DIR, 'subdir', `sub-${j}.json`),
        'utf8',
      );
      JSON.parse(content);
    }
    ops += 10;

    // Sync write (cache files, lock files)
    const cacheFile = path.join(TEMP_DIR, 'cache', `cache-${i % 20}.json`);
    fs.writeFileSync(
      cacheFile,
      JSON.stringify({ key: `key-${i}`, value: i * 42, ts: Date.now() }),
    );
    ops++;
  }
  return ops;
}

// Workload 3: stat / access / exists (file existence checks)
async function workloadStatAccess(iterations) {
  let ops = 0;

  for (let i = 0; i < iterations; i++) {
    // stat (used by express.static, require.resolve, etc.)
    await fsp.stat(path.join(TEMP_DIR, `file-${i % 50}.txt`));
    ops++;

    // stat directory
    await fsp.stat(path.join(TEMP_DIR, 'subdir'));
    ops++;

    // Access check (permission verification)
    try {
      await fsp.access(
        path.join(TEMP_DIR, `file-${i % 50}.txt`),
        fs.constants.R_OK,
      );
    } catch {
      /* expected for some */
    }
    ops++;

    // lstat (symlink-aware, used by tools)
    await fsp.lstat(path.join(TEMP_DIR, `file-${i % 50}.txt`));
    ops++;

    // Stat non-existent (cache miss pattern)
    try {
      await fsp.stat(path.join(TEMP_DIR, `nonexistent-${i}.txt`));
    } catch {
      /* expected */
    }
    ops++;

    // Sync variants (require.resolve uses these)
    fs.statSync(path.join(TEMP_DIR, `file-${i % 50}.txt`));
    ops++;

    fs.existsSync(path.join(TEMP_DIR, `file-${i % 50}.txt`));
    ops++;

    fs.existsSync(path.join(TEMP_DIR, `nonexistent-${i}.txt`));
    ops++;
  }
  return ops;
}

// Workload 4: readdir (directory listing, build tools, file watchers)
async function workloadReaddir(iterations) {
  let ops = 0;

  for (let i = 0; i < iterations; i++) {
    // Simple readdir
    await fsp.readdir(TEMP_DIR);
    ops++;

    // Readdir with file types (common for file tree traversal)
    await fsp.readdir(TEMP_DIR, { withFileTypes: true });
    ops++;

    // Readdir subdirectory
    await fsp.readdir(path.join(TEMP_DIR, 'subdir'), { withFileTypes: true });
    ops++;

    // Sync variant (build tools)
    fs.readdirSync(TEMP_DIR);
    ops++;

    // Recursive readdir (newer API, gaining adoption)
    if (i % 5 === 0) {
      await fsp.readdir(TEMP_DIR, { recursive: true });
      ops++;
    }
  }
  return ops;
}

// Workload 5: mkdir / rmdir / rename / copy (build tool patterns)
async function workloadDirectoryOps(iterations) {
  let ops = 0;

  for (let i = 0; i < iterations; i++) {
    const dir = path.join(TEMP_DIR, `build-${i}`);

    // mkdir (build output directories)
    await fsp.mkdir(dir, { recursive: true });
    ops++;

    // Create file in new dir
    const file = path.join(dir, 'output.txt');
    await fsp.writeFile(file, `Build output ${i}`);
    ops++;

    // Rename (atomic file update pattern)
    const newFile = path.join(dir, 'output.final.txt');
    await fsp.rename(file, newFile);
    ops++;

    // Copy file
    const copyDest = path.join(dir, 'output.backup.txt');
    await fsp.copyFile(newFile, copyDest);
    ops++;

    // rmdir (cleanup)
    await fsp.rm(dir, { recursive: true, force: true });
    ops++;
  }
  return ops;
}

// Workload 6: Stream read/write (large file processing, log files)
async function workloadStreams(iterations) {
  let ops = 0;

  for (let i = 0; i < iterations; i++) {
    // Read stream (file download, static serving of large files)
    await new Promise((resolve, reject) => {
      const rs = fs.createReadStream(path.join(TEMP_DIR, 'large.dat'), {
        highWaterMark: 16 * 1024,
      });
      rs.on('data', () => {});
      rs.on('end', () => {
        ops++;
        resolve();
      });
      rs.on('error', reject);
    });

    // Write stream (log file appending)
    await new Promise((resolve, reject) => {
      const ws = fs.createWriteStream(
        path.join(TEMP_DIR, 'logs', `app-${i % 5}.log`),
        { flags: 'a' },
      );
      for (let j = 0; j < 50; j++) {
        ws.write(
          `[${new Date().toISOString()}] INFO: Request processed in ${Math.random() * 100}ms\n`,
        );
      }
      ws.end(() => {
        ops++;
        resolve();
      });
      ws.on('error', reject);
    });

    // Pipe pattern (file copy via streams)
    if (i % 3 === 0) {
      await new Promise((resolve, reject) => {
        const rs = fs.createReadStream(path.join(TEMP_DIR, 'medium.txt'));
        const ws = fs.createWriteStream(
          path.join(TEMP_DIR, `pipe-copy-${i}.txt`),
        );
        rs.pipe(ws);
        ws.on('finish', () => {
          ops++;
          resolve();
        });
        ws.on('error', reject);
      });
    }
  }
  return ops;
}

// Workload 7: Path operations (used in every file operation)
function workloadPathOps(iterations) {
  let ops = 0;
  const testPaths = [
    '/usr/local/lib/node_modules/express/lib/router/index.js',
    'C:\\Users\\user\\project\\node_modules\\lodash\\lodash.js',
    '../relative/path/to/file.js',
    './src/components/Button/index.tsx',
    'https://example.com/path/to/resource?query=value#hash',
    '/path/with spaces/and (parens)/file name.txt',
  ];

  for (let i = 0; i < iterations; i++) {
    for (const p of testPaths) {
      path.resolve(p);
      path.dirname(p);
      path.basename(p);
      path.extname(p);
      path.parse(p);
      path.normalize(p);
      path.isAbsolute(p);
      ops += 7;
    }

    // path.join (the most common path operation)
    path.join('src', 'components', 'Button', 'index.tsx');
    path.join(TEMP_DIR, 'subdir', 'nested', 'file.txt');
    path.join('..', '..', 'node_modules', '.cache');
    ops += 3;

    // path.relative (monorepo/build tool pattern)
    path.relative(
      '/project/packages/core/src',
      '/project/packages/utils/src/helpers.js',
    );
    path.relative(TEMP_DIR, path.join(TEMP_DIR, 'subdir', 'file.txt'));
    ops += 2;

    // path.format (inverse of parse)
    path.format({
      root: '/',
      dir: '/home/user',
      base: 'file.txt',
      ext: '.txt',
      name: 'file',
    });
    ops++;
  }
  return ops;
}

async function main() {
  console.log('[pgo-fs] Starting file system workload...');

  setup();
  const startTime = Date.now();
  let totalOps = 0;
  let round = 0;
  const remaining = () => DURATION_MS - (Date.now() - startTime);

  try {
    while (remaining() > 0) {
      round++;
      const scale = Math.max(0.1, remaining() / DURATION_MS);
      const iterScale = (base) => Math.max(1, Math.floor(base * scale));

      // Path ops first — pure CPU, exercises V8 string handling
      if (round === 1) console.log('[pgo-fs] Running path operations...');
      totalOps += workloadPathOps(iterScale(2000));
      if (remaining() <= 0) break;

      // Sync reads (startup/require pattern)
      if (round === 1) console.log('[pgo-fs] Running sync read/write...');
      totalOps += workloadReadWriteSync(iterScale(100));
      if (remaining() <= 0) break;

      // Async reads/writes (most common runtime pattern)
      if (round === 1) console.log('[pgo-fs] Running async read/write...');
      totalOps += await workloadReadWriteAsync(iterScale(50));
      if (remaining() <= 0) break;

      // stat/access (middleware, require.resolve)
      if (round === 1) console.log('[pgo-fs] Running stat/access...');
      totalOps += await workloadStatAccess(iterScale(100));
      if (remaining() <= 0) break;

      // Directory operations
      if (round === 1) console.log('[pgo-fs] Running readdir...');
      totalOps += await workloadReaddir(iterScale(100));
      if (remaining() <= 0) break;

      if (round === 1) console.log('[pgo-fs] Running directory ops...');
      totalOps += await workloadDirectoryOps(iterScale(30));
      if (remaining() <= 0) break;

      // Stream operations
      if (round === 1) console.log('[pgo-fs] Running stream read/write...');
      totalOps += await workloadStreams(iterScale(20));
    }

    const elapsed = (Date.now() - startTime) / 1000;
    console.log(
      `[pgo-fs] Completed ${totalOps} fs operations in ${elapsed.toFixed(1)}s (${(totalOps / elapsed).toFixed(0)} ops/s) [${round} rounds]`,
    );
  } finally {
    cleanup();
  }
}

main().catch((err) => {
  console.error('[pgo-fs] Error:', err);
  cleanup();
  process.exit(1);
});