Table of Contents

Stress Test

Real-time Usage

For accurate and detailed CPU and GPU usage metrics, it is recommended to use native system tools such as Task Manager on Windows, Activity Monitor on macOS, or third-party applications designed for performance monitoring.

To learn more, read the below section on Why Real-time Usage is Not Available.

Toggle

About

This Stress Test web application allows users to initiate CPU and GPU stress tests directly from their browsers. By clicking the provided buttons, users can start and stop stress tests for either the CPU, the GPU, or both simultaneously. The application leverages web workers for CPU stress testing and WebGL via offscreen canvases for GPU stress testing.

How It Works

The CPU stress test creates multiple web workers that continuously execute a busy loop, consuming CPU resources. The number of web workers is determined by the number of logical processors available, defaulting to 4 if the hardware information is unavailable.

The GPU stress test utilizes WebGL to render continuous frames on an offscreen canvas. It creates and compiles shaders, sets up a drawing program, and draws triangles in a loop to keep the GPU engaged.

Limitations

While the CPU stress test can effectively utilize all available CPU cores, the GPU stress test may not fully maximize GPU usage at all times. The efficiency of the GPU stress test can be influenced by the browser's WebGL implementation and the underlying hardware.

Why Real-time Usage is Not Available

Monitoring real-time CPU and GPU usage directly within a web browser is challenging due to several limitations:

  • Security and Privacy: Browsers are designed to protect user privacy and security. Allowing web pages to access detailed system information such as real-time CPU and GPU usage could expose sensitive data and make the system vulnerable to security risks.
  • Sandboxing: Browsers run web applications in a sandboxed environment to prevent them from accessing the underlying system resources directly. This ensures that malicious scripts cannot interfere with or gather data from the host machine.
  • APIs and Permissions: There are no standardized web APIs available that provide real-time CPU and GPU usage metrics. Existing performance APIs offer limited insights that are not sufficient for precise monitoring.
  • Resource Constraints: Real-time monitoring can be resource-intensive and could affect the performance and responsiveness of the web application itself.

For accurate and detailed CPU and GPU usage metrics, it is recommended to use native system tools such as Task Manager on Windows, Activity Monitor on macOS, or third-party applications designed for performance monitoring.

Implementation Details

The implementation begins by initializing global variables to manage CPU workers and the GPU worker. Event listeners are added to the buttons to trigger the respective stress tests. The startCPUTest function checks if CPU workers are running, and if not, it spawns new workers that execute a busy loop. The startGPUTest function initializes a web worker to create an offscreen WebGL context and renders frames in a loop.

Both functions have corresponding stop functions to terminate the workers and update the button texts. Additionally, the startBothTest function can start or stop both stress tests simultaneously based on the current state. The application also includes a beforeunload event listener to ensure that all workers are terminated when the page is closed or refreshed.

Code

HTML

<button id="cpuButton" onclick="startCPUTest()">Start CPU Stress Test</button>
<button id="gpuButton" onclick="startGPUTest()">Start GPU Stress Test</button>
<button id="bothButton" onclick="startBothTest()">Start CPU & GPU Stress Test</button>

JS

let cpuWorkers = [];
let gpuWorker = null;

function startCPUTest() {
  console.log("startCPUTest called");
  if (cpuWorkers.length === 0) {
    console.log("Starting CPU stress test");
    document.getElementById('cpuButton').innerText = 'Stop CPU Stress Test';

    const numWorkers = navigator.hardwareConcurrency || 4;  // Use number of logical processors or default to 4
    for (let i = 0; i < numWorkers; i++) {
      const worker = new Worker(URL.createObjectURL(new Blob([`
        self.onmessage = function() {
          while (true) {
            // Busy loop to simulate CPU load
            Math.sqrt(Math.random());
          }
        };
      `])));
      worker.postMessage({});
      cpuWorkers.push(worker);
    }
    updateBothButtonText();
  } else {
    stopCPUTest();
  }
}

function startGPUTest() {
  console.log("startGPUTest called");
  if (gpuWorker === null) {
    console.log("Starting GPU stress test");
    document.getElementById('gpuButton').innerText = 'Stop GPU Stress Test';

    gpuWorker = new Worker(URL.createObjectURL(new Blob([`
      self.onmessage = function() {
        function createShader(gl, type, source) {
          const shader = gl.createShader(type);
          gl.shaderSource(shader, source);
          gl.compileShader(shader);
          if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
            return null;
          }
          return shader;
        }

        function createProgram(gl, vertexShader, fragmentShader) {
          const program = gl.createProgram();
          gl.attachShader(program, vertexShader);
          gl.attachShader(program, fragmentShader);
          gl.linkProgram(program);
          if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program));
            return null;
          }
          return program;
        }

        const canvas = new OffscreenCanvas(256, 256);
        const gl = canvas.getContext('webgl');
        const vertexShaderSource = \`
          attribute vec4 a_position;
          void main() {
            gl_Position = a_position;
          }
        \`;
        const fragmentShaderSource = \`
          precision mediump float;
          void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
          }
        \`;
        const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
        if (!vertexShader || !fragmentShader) {
          console.error('Failed to create shaders');
          return;
        }
        const program = createProgram(gl, vertexShader, fragmentShader);
        if (!program) {
          console.error('Failed to create program');
          return;
        }

        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        const positions = [
          -1, -1,
          1, -1,
          -1, 1,
          -1, 1,
          1, -1,
          1, 1,
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

        const positionLocation = gl.getAttribLocation(program, "a_position");
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.useProgram(program);
        while (true) {
          gl.drawArrays(gl.TRIANGLES, 0, 6);
        }
      };
    `])));
    gpuWorker.postMessage({});
    updateBothButtonText();
  } else {
    stopGPUTest();
  }
}

function startBothTest() {
  console.log("startBothTest called");
  if (cpuWorkers.length > 0 && gpuWorker !== null) {
    stopBothTest();
  } else {
    if (cpuWorkers.length === 0) {
      startCPUTest();
    }
    if (gpuWorker === null) {
      startGPUTest();
    }
  }
}

function stopCPUTest() {
  console.log("stopCPUTest called");
  cpuWorkers.forEach(worker => worker.terminate());
  cpuWorkers = [];
  document.getElementById('cpuButton').innerText = 'Start CPU Stress Test';
  updateBothButtonText();
}

function stopGPUTest() {
  console.log("stopGPUTest called");
  if (gpuWorker !== null) {
    gpuWorker.terminate();
    gpuWorker = null;
    document.getElementById('gpuButton').innerText = 'Start GPU Stress Test';
  }
  updateBothButtonText();
}

function stopBothTest() {
  console.log("stopBothTest called");
  stopCPUTest();
  stopGPUTest();
}

function updateBothButtonText() {
  if (cpuWorkers.length > 0 && gpuWorker !== null) {
    document.getElementById('bothButton').innerText = 'Stop CPU & GPU Stress Test';
  } else {
    document.getElementById('bothButton').innerText = 'Start CPU & GPU Stress Test';
  }
}

window.addEventListener('beforeunload', () => {
  stopCPUTest();
  stopGPUTest();
});