Full Code of sitespeedio/throttle for AI

main 255090d96622 cached
25 files
32.9 KB
10.4k tokens
20 symbols
1 requests
Download .txt
Repository: sitespeedio/throttle
Branch: main
Commit: 255090d96622
Files: 25
Total size: 32.9 KB

Directory structure:
gitextract_34mq2ed_/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── docker.yml
│       ├── install.yml
│       ├── linux.yml
│       └── osx.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bin/
│   └── index.js
├── eslint.config.js
├── lib/
│   ├── execFile.js
│   ├── index.js
│   ├── localHostPfctl.js
│   ├── localHostTc.js
│   ├── pfctl.js
│   ├── shell.js
│   ├── sudo.js
│   └── tc.js
├── package.json
├── release.sh
└── test/
    ├── Dockerfile
    ├── config.json
    └── start.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2


================================================
FILE: .github/workflows/docker.yml
================================================
name: Run Docker
on:
  push:
    branches:
    - main
  pull_request:
    branches:
    - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
      with:
        egress-policy: block
        allowed-endpoints: >
          archive.ubuntu.com:80
          auth.docker.io:443
          github.com:443
          production.cloudflare.docker.com:443
          registry-1.docker.io:443
          registry.npmjs.org:443
          security.ubuntu.com:80
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
    - name: Build the container
      run: docker build -f test/Dockerfile . -t sitespeedio/throttle
    - name: Test Throttle
      run: docker run --cap-add=NET_ADMIN sitespeedio/throttle

================================================
FILE: .github/workflows/install.yml
================================================
name: Install latest release
on:
  push:
    branches:
    - main
  pull_request:
    branches:
    - main
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [24.x]
    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
      with:
        egress-policy: block
        allowed-endpoints: >
          registry.npmjs.org:443
    - name: Install throttle
      run: npm install @sitespeed.io/throttle -g
    - name: Install dependencies
      run: sudo apt-get install -y net-tools
    - name: Show interfaces
      run: sudo ip r
    - name: Show version
      run: throttle --version
    - name: Test cable
      run: throttle cable

================================================
FILE: .github/workflows/linux.yml
================================================
name: Linux
on:
  push:
    branches:
    - main
  pull_request:
    branches:
    - main
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [20.x, 22.x, 24.x]
    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
      with:
        egress-policy: audit
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
      with:
        node-version: ${{ matrix.node-version }}
    - name: Install throttle
      run: npm ci
    - name: Install dependencies
      run: sudo apt-get install -y net-tools
    - name: Verify lint
      run: npm run lint
    - name: Show interfaces
      run: sudo ip route show
    - name: Get default interface
      run: sudo ip route | awk '/default/ {print $5; exit}' | tr -d '\n'
    - name: Test cable 
      run: LOG_THROTTLE=true bin/index.js cable && bin/index.js stop
    - name: Test configuration 
      run: LOG_THROTTLE=true bin/index.js throttle --up 330 --down 780 --rtt 200 && bin/index.js stop
    - name: Test localhost 
      run: LOG_THROTTLE=true bin/index.js --rtt 200 --localhost && bin/index.js stop --localhost
    - name: Test config file
      run: LOG_THROTTLE=true bin/index.js --config test/config.json && bin/index.js stop
    - name: Test packet loss
      run: LOG_THROTTLE=true bin/index.js throttle --up 330 --down 780 --rtt 200 --packetLoss 10 && bin/index.js stop
    - name: Test packet loss and profile
      run: LOG_THROTTLE=true bin/index.js 3g --packetLoss 10 && bin/index.js stop


================================================
FILE: .github/workflows/osx.yml
================================================
name: OSX
on:
  push:
    branches:
    - main
  pull_request:
    branches:
    - main
jobs:
  build:
    runs-on: macos-latest
    steps:
    - name: Harden Runner
      uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
      with:
        egress-policy: audit
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
    - name: Use Node.js
      uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
      with:
        node-version: '20.x'
    - name: Install dependencies 
      run: npm ci
    - name: Test 3g 
      run: bin/index.js 3g && bin/index.js stop
    - name: Test configuration 
      run: bin/index.js throttle --up 330 --down 780 --rtt 200 && bin/index.js stop
    - name: Test localhost
      run: bin/index.js throttle --rtt 200 --localhost && bin/index.js stop --localhost
    - name: Test profile and packet loss 
      run: bin/index.js 3g --packetLoss 5 && bin/index.js stop
    - name: Test configuration  and packet loss
      run: bin/index.js throttle --up 330 --down 780 --rtt 200 --packetLoss 5 && bin/index.js stop


================================================
FILE: .gitignore
================================================
node_modules

================================================
FILE: .npmignore
================================================
test/*
release.sh


================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG - throttle

# 6.0.0 - 2026-03-23
### Added
* Added IPv6 throttling support on macOS [#94](https://github.com/sitespeedio/throttle/pull/94) and Linux [#100](https://github.com/sitespeedio/throttle/pull/100).
* Apply packet loss also to the upload interface [#69](https://github.com/sitespeedio/throttle/pull/69).

### Fixed
* More robust detection of the default network interface on Linux, falling back to global IP address lookup when the default route is unavailable [#104](https://github.com/sitespeedio/throttle/pull/104).
* Fixed README profile values for DSL and Edge to match actual code, and fixed Node.js API examples to use named imports.

### Tech
* Updated ESLint to 10, Prettier to 3, and migrated to flat config [#101](https://github.com/sitespeedio/throttle/pull/101) [#103](https://github.com/sitespeedio/throttle/pull/103).
* Updated GitHub Actions to use actions/checkout@v4 and actions/setup-node@v4.

# 5.0.1 - 2024-08-26
### Fixed
* Smarter ifb0 handling on Linux [#93](https://github.com/sitespeedio/throttle/pull/93).

# 5.0.0 - 2022-07-02
### Changed
* Dropped support for NodeJS 12 since it's unmaintaned.

### Tech
* Code cleanup using eslint-plugin-unicorn.
# 4.0.3 - 2022-06-29
### Fixed
* Fixed `--version` so it actually shows the version again [#75](https://github.com/sitespeedio/throttle/pull/75).
# 4.0.2 - 2022-06-21
### Fixed
* The E6 Module convertion in 4.0 broke version check (`--version`) making installs failed. PR [#74](https://github.com/sitespeedio/throttle/pull/74) is a temporaty fix for that.

# 4.0.1 - 2022-06-17
### Fixed
* Removed the dependecy of route. Using `ip route` instead and we already have `ip` as a requirement.
# 4.0.0 - 2022-04-14
### Changed
* Converting the project to ES6 module. If you run Throttle by command line it will work exactly as before except that we dropped support for NodeJS 10 [#67](https://github.com/sitespeedio/throttle/pull/67). You can read [Sindre Sorhus ESM package guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).

### Fixed
* Updated dev dependencies to latest versions [#68](https://github.com/sitespeedio/throttle/pull/68)

# 3.1.1 - 2022-04-19
### Fixed
* Updated minimist dependency.
# 3.1.0 - 2021-12-26
### Added
* Add support for setting packet loss. Add `--packetLoss` and set the loss in percentage.
# 3.0.0 - 2021-08-09

### Changed
* When you run on Mac OS we changed so localhost traffic is not throttled by default. To throttle on localhost use `--localhost`. This makes sence if you use WebDriver to drive your browser on your local machine. And Mac OS now works the same as Linux.
# 2.1.2 - 2021-07-29
### Fixed
* Fix breakage when multiple default routes are declared , thank you [Andy Richardson](https://github.com/andyrichardson) for PR [#62](https://github.com/sitespeedio/throttle/pull/62).
# 2.1.1 - 2021-03-19
### Fixed
* A better check for missing net-tools on Linux, thank you [Radu Micu](https://github.com/radum) for the PR [#56](https://github.com/sitespeedio/throttle/pull/56).
# 2.1.0 - 2021-03-10
## Added
* Added support for config files. Use `--config config.json` to read configuration setup from a file.
# 2.0.2 - 2020-09-10
## Fixed
* Make the install as small as possible.

# 2.0.1 - 2020-08-22
## Fixed
* Removed the execa dependency to make sure we have minimal dependencies [#53](https://github.com/sitespeedio/throttle/pull/53).

# 2.0.0 - 2020-08-14
### Changed
* You can now set one of up/down/rtt if you like as introduced by [Iñaki Baz Castillo](https://github.com/ibc) - thank you! Implemented in [#46](https://github.com/sitespeedio/throttle/pull/46). This also remove the default profile meaning there's a change in behavior if you run throttle without any parameters. Before a default profile was used but now you get an error (you know need to set a profile or set up/down or rtt).

### Added
* You can use ```--log``` to log all networks commands [#45](https://github.com/sitespeedio/throttle/pull/45).
* Added support to add delay on for localhost on OS X. Use ```--rtt 100 --localhost``` [#51](https://github.com/sitespeedio/throttle/pull/51).

### Fixed
* Add missing await when removing the ingress if removing root fails for tc [#47](https://github.com/sitespeedio/throttle/pull/47).
* Better error handling in the CLI [#48](https://github.com/sitespeedio/throttle/pull/48). Make sure that the exit code is > 0 if setting the throttling fails.

### Tech
* Removed configuration file for pfctl to make it easier to set up dynamic dummynet [#49](https://github.com/sitespeedio/throttle/pull/49)

## 1.1.0 2020-07-29
### Fixed
* Updated execa to 4.0.3.
* Removed log message "using default profile"

### Added
* You can log all the commands that sets up the throttling by setting ```LOG_THROTTLE=true``` [#44](https://github.com/sitespeedio/throttle/pull/44).

## 1.0.3 2020-06-20
### Fixed
* Updated minimist

## 1.0.2 2020-03-09
### Fixed
* Fixed dependency tree and npm-shrinkwrap to only hold dependencies for production.

## 1.0.1 2020-02-03
### Fixed
* Another fix to 2g to get that right, thank you Matt! [#37](https://github.com/sitespeedio/throttle/pull/37).

## 1.0.0 2020-02-03
### Changed
* 2g speed was updated to become more usable and follow WPT [#36](https://github.com/sitespeedio/throttle/pull/36) - thank you [Matt Hobbs](https://github.com/Nooshu) for the PR.

### Added
* More pre-made profiles: dsl, 3gem, 4g, lte, edge, dial, fois [#36](https://github.com/sitespeedio/throttle/pull/36) - thank you [Matt Hobbs](https://github.com/Nooshu) for the PR.

## 0.5.4 2019-08-26
### Fixed
* Another execa fix, hopefully fixing the last thing + added more tests in Travis.

## 0.5.3 2019-08-26
### Fixed
* Over optimistic Execa upgraded caused Throttle to stop working [#32](https://github.com/sitespeedio/throttle/pull/32).

## 0.5.2 2019-08-26
### Fixed
* Updated dependencies [#31](https://github.com/sitespeedio/throttle/pull/31). 

## 0.5.1 2019-04-23
### Fixed
* Calling stop on Linux throwed error see [#20](https://github.com/sitespeedio/throttle/issues/20) and fixed by [Iñaki Baz Castillo](https://github.com/ibc), thank you!

## 0.5.0 2018-12-07
### Added
* Simplified profile/stop/help. You can now start with: ```throttle $profile``` and stop with ```throttle stop```

## 0.4.3 2018-09-01
### Fixed
* Upload throttling was wrong on Mac OS X, thank you [Paul](https://github.com/paulz) for the [PR](https://github.com/sitespeedio/throttle/pull/16).

## 0.4.2 2018-05-30
### Fixed
* Catching when ifb has already been setup.

## 0.4.1 2018-05-30
### Fixed
* Another go at trying to try/catch failing settings.

## 0.4.0 2018-05-30
### Added
* Rewrite to async/await from promises

## 0.3.0 2018-04-12

### Fixed
* Ensure setup has completed fully before returning when starting throttling with tc.
* Always return Promises from start() and stop(), even in case of errors.
* Typo in the CLI help
* Simpler way to set connectivity with tc

## 0.2.0 2017-10-31

### Added
* You can now see the version with --version
* You can now use pre defined profiles.

## 0.1.0 2017-10-13

### Fixed
* Always remove filters before we try to set them.


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2017 Peter Hedenskog & Tobias Lidskog

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Simulate slow network connections on Linux and Mac OS X

[![Linux](https://github.com/sitespeedio/throttle/workflows/Linux/badge.svg)](https://github.com/sitespeedio/throttle/actions/workflows/linux.yml)
[![OSX](https://github.com/sitespeedio/throttle/workflows/OSX/badge.svg)](https://github.com/sitespeedio/throttle/actions/workflows/osx.yml)
[![Docker](https://github.com/sitespeedio/throttle/workflows/Run%20Docker/badge.svg)](https://github.com/sitespeedio/throttle/actions/workflows/docker.yml)

Inspired by [tylertreat/Comcast](https://github.com/tylertreat/Comcast), the [connectivity setting in the WPTAgent](https://github.com/WPO-Foundation/wptagent/blob/master/internal/traffic_shaping.py) and [sltc](https://github.com/sitespeedio/sltc).

Throttle uses *pfctl* on Mac and *tc* on Linux to simulate different network speeds. On Linux you also need *ip* for Throttle to work (install using `sudo apt-get install -y net-tools`).

You can set the download/upload speed and/or RTT. Upload/download is in kbit/s and RTT in ms.

Use with [latest NodeJS LTS](https://nodejs.org/en/).

## Install

```
npm install @sitespeed.io/throttle -g
```

On OSX, add these lines to ```/etc/pf.conf``` if they don't exist, to prevent the ```pfctl: Syntax error in config file: pf rules not loaded``` error when you try to run throttle

```
pf_enable="YES"
pflog_enable="YES"
```

On Linux you need to make sure *ip* and *route* is installed (install using `sudo apt-get install -y net-tools`).

## Start simulate a slower network connection

Here is an example for running with 3G connectivity. Remember: Throttle will use sudo so your user will need sudo rights.

```
throttle --up 330 --down 780 --rtt 200
```

## Pre made profiles
To make it easier we have pre made profiles, check them out by *throttle --help*:

```
--profile         Premade profiles, set to one of the following
                     3g: up:768 down:1600 rtt:150
                     3gfast: up:768 down:1600 rtt:75
                     3gslow: up:400 down:400 rtt:200
                     2g: up:256 down:280 rtt:400
                     cable: up:1000 down:5000 rtt:14
                     dsl: up:384 down:1500 rtt:25
                     3gem: up:400 down:400 rtt:200
                     4g: up:9000 down:9000 rtt:85
                     lte: up:12000 down:12000 rtt:35
                     edge: up:200 down:240 rtt:420
                     dial: up:30 down:49 rtt:60
                     fois: up:5000 down:20000 rtt:2
```

You can start throttle with one of the premade profiles:

```
throttle --profile 3gslow
```

or even simpler
```
throttle 3gslow
```

## Add packet loss

By default there's no packet loss. That is by design: If you want to use Throttle and have the same network speed, packet loss is no good. However if you want to simalate a really crappy network you probably want to add packet loss. You do that with the `--packetLoss` option. You set the packet loss in percent.

```
throttle --profile 3gslow --packetLoss 5
```


## Use a configuration file
You can also use a configuration file with your settings. Use `--config` to map your config file to throttle.

config.json
```json
{
    "up": 330 ,
    "down": 200,
    "rtt": 1000
}
```

And then run:
```
throttle --config config.json
```

## Stop simulate the network
Stopping is as easy as giving the parameter *stop* to throttle.

```
throttle --stop
```

or

```
throttle stop
```

## Add delay on your localhost 
This is useful if you test a local web server or run [WebPageReplay](https://github.com/catapult-project/catapult/blob/master/web_page_replay_go/README.md) and want to add some latency to your tests.

```
throttle --rtt 200 --localhost
```

## Stop adding delay on localhost

```
throttle --stop --localhost
```

## Use directly in NodeJS

```javascript
import { start, stop } from '@sitespeed.io/throttle'
// Returns a promise
start({up: 360, down: 780, rtt: 200}).then(() => ...
```

or

```javascript
import { start, stop } from '@sitespeed.io/throttle'
const options = {up: 360, down: 780, rtt: 200};
await start(options);
// Do your thing and then stop
await stop();
```

## Log all commands
You can log all the commands that sets up the throttling by setting `LOG_THROTTLE=true`.

```
LOG_THROTTLE=true throttle 3gslow
```

or 

```
throttle 3gslow --log
```

## Run in Docker (on Linux)

Make sure to run ```sudo modprobe ifb numifbs=1``` before you start the container.

And then when you actually start your Docker container, give it the right privileges with ```--cap-add=NET_ADMIN```.


================================================
FILE: bin/index.js
================================================
#!/usr/bin/env node

import { readFileSync } from 'node:fs';
import minimist from 'minimist';
import { stop, start } from '../lib/index.js';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const version = require('../package.json').version;

const profiles = {
  '3g': {
    down: 1600,
    up: 768,
    rtt: 150
  },
  '3gfast': {
    down: 1600,
    up: 768,
    rtt: 75
  },
  '3gslow': {
    down: 400,
    up: 400,
    rtt: 200
  },
  '2g': {
    down: 280,
    up: 256,
    rtt: 400
  },
  cable: {
    down: 5000,
    up: 1000,
    rtt: 14
  },
  dsl: {
    down: 1500,
    up: 384,
    rtt: 25
  },
  '3gem': {
    down: 400,
    up: 400,
    rtt: 200
  },
  '4g': {
    down: 9000,
    up: 9000,
    rtt: 85
  },
  lte: {
    down: 12_000,
    up: 12_000,
    rtt: 35
  },
  edge: {
    down: 240,
    up: 200,
    rtt: 420
  },
  dial: {
    down: 49,
    up: 30,
    rtt: 60
  },
  fois: {
    down: 20_000,
    up: 5000,
    rtt: 2
  }
};

const argv = minimist(process.argv.slice(2), {
  boolean: ['stop', 'localhost']
});

async function run(argv) {
  if (argv.help || argv._[0] === 'help') {
    console.log('   Set the connectivity using the throttler (pfctl/tc)');
    console.log('   Usage: throttler [options]');
    console.log(
      '   If you run in Docker throttler will only work on a Linux host'
    );
    console.log('   In Docker make sure to run: sudo modprobe ifb numifbs=1');
    console.log('   And run your container with --cap-add=NET_ADMIN\n');
    console.log('   Options:');
    console.log('   --stop            Remove all settings');
    console.log('   --up              Upload in Kbit/s ');
    console.log('   --down            Download Kbit/s');
    console.log('   --rtt             RTT in ms');
    console.log('   --packetLoss      Packet loss in %. Default is 0');
    console.log(
      '   --profile         Premade profiles, set to one of the following'
    );
    console.log('   --config          Path to config file');
    for (const profile of Object.keys(profiles)) {
      console.log(
        '                     ' +
          profile +
          ': ' +
          'up:' +
          profiles[profile].up +
          ' down:' +
          profiles[profile].down +
          ' rtt:' +
          profiles[profile].rtt
      );
    }
    console.log('   --log             Log all network commands to the console');
  } else if (argv.version) {
    console.log(`${version}`);
  } else {
    if (argv.stop || argv._[0] === 'stop') {
      const options = {
        localhost: argv.localhost
      };
      await stop(options);
      console.log('Stopped throttler');
    } else {
      let options;
      if (argv.log) {
        process.env.LOG_THROTTLE = true;
      }
      if (argv.profile in profiles || argv._[0] in profiles) {
        options = profiles[argv.profile || argv._[0]];

        if (argv.packetLoss) {
          options.packetLoss = argv.packetLoss;
        }

        console.log('Using profile ' + (argv.profile || argv._[0]));
      } else if (argv.config) {
        try {
          const data = readFileSync(argv.config, 'utf8');
          options = JSON.parse(data);
        } catch (error) {
          console.error(error);
          process.exitCode = 1;
        }
      } else {
        options = {
          up: argv.up,
          down: argv.down,
          rtt: argv.rtt,
          localhost: argv.localhost,
          packetLoss: argv.packetLoss || 0
        };
      }

      try {
        await start(options);
        if (options.localhost) {
          console.log(`Started throttler on localhost RTT:${options.rtt}ms `);
        } else {
          let message = 'Started throttler:';
          if (options.down !== undefined) {
            message += ` Down:${options.down}kbit/s`;
          }
          if (options.up !== undefined) {
            message += ` Up:${options.up}kbit/s`;
          }
          if (options.rtt !== undefined) {
            message += ` RTT:${options.rtt}ms`;
          }
          if (options.packetLoss !== undefined) {
            message += ` PacketLoss:${options.packetLoss}%`;
          }
          console.log(message);
        }
      } catch (error) {
        console.error(error);
        process.exitCode = 1;
      }
    }
  }
}

await run(argv);


================================================
FILE: eslint.config.js
================================================
import prettier from 'eslint-plugin-prettier';
import unicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import js from '@eslint/js';

export default [
  js.configs.recommended,
  unicorn.configs.recommended,
  {
    plugins: {
      prettier
    },

    languageOptions: {
      globals: {
        ...globals.node
      },

      ecmaVersion: 'latest',
      sourceType: 'module'
    },

    rules: {
      'prettier/prettier': [
        'error',
        {
          singleQuote: true,
          trailingComma: 'none'
        }
      ],

      'no-extra-semi': 'off',
      'no-mixed-spaces-and-tabs': 'off',
      'no-unexpected-multiline': 'off',
      'no-return-await': 'error',
      'unicorn/filename-case': 'off'
    }
  }
];


================================================
FILE: lib/execFile.js
================================================
import { promisify } from 'node:util';
import { execFile } from 'node:child_process';
const execFilePromisified = promisify(execFile);

export default function shell(command, options) {
  return execFilePromisified(command, options);
}


================================================
FILE: lib/index.js
================================================
import { platform } from 'node:os';
import { start as startPfctl, stop as stopPfctl } from './pfctl.js';
import { start as startTc, stop as stopTc } from './tc.js';
import {
  start as startTcLocalhost,
  stop as stopTcLocalhost
} from './localHostTc.js';
import {
  start as startPfctlLocalhost,
  stop as stopPfctlLocalhost
} from './localHostPfctl.js';

function verify(options) {
  if (options.localhost) {
    if (!Number.isInteger(options.rtt)) {
      throw new TypeError('You need to set rtt as an integer for localhost');
    }
  } else if (
    (options.up && !Number.isInteger(options.up)) ||
    (options.down && !Number.isInteger(options.down)) ||
    (options.rtt && !Number.isInteger(options.rtt))
  ) {
    throw new Error('Input values needs to be integers');
  } else if (!options.up && !options.down && !options.rtt && !options.stop) {
    throw new Error('You need to at least set one of up/down/rtt.');
  }
}

export async function start(options = {}) {
  verify(options);

  switch (platform()) {
    case 'darwin': {
      if (options.localhost) {
        return startPfctlLocalhost(options.rtt);
      }
      return startPfctl(
        options.up,
        options.down,
        options.rtt,
        options.packetLoss
      );
    }

    case 'linux': {
      return options.localhost
        ? startTcLocalhost(options.rtt)
        : startTc(options.up, options.down, options.rtt, options.packetLoss);
    }

    default: {
      throw new Error('Platform ' + platform() + ' not supported');
    }
  }
}
export async function stop(options = {}) {
  switch (platform()) {
    case 'darwin': {
      return options.localhost ? stopPfctlLocalhost() : stopPfctl();
    }

    case 'linux': {
      return options.localhost ? stopTcLocalhost() : stopTc();
    }

    default: {
      throw new Error('Platform ' + platform() + ' not supported');
    }
  }
}


================================================
FILE: lib/localHostPfctl.js
================================================
import sudo from './sudo.js';
import shell from './shell.js';

export async function start(rtt) {
  const halfWayRTT = rtt / 2;

  await stop();

  await sudo('dnctl', '-q', 'flush');
  await sudo('dnctl', '-q', 'pipe', 'flush');

  await sudo(
    'dnctl',
    'pipe',
    1,
    'config',
    'delay',
    `${halfWayRTT}ms`,
    'noerror'
  );

  await shell(
    'echo "dummynet out from any to 127.0.0.1 pipe 1" | sudo pfctl -f -'
  );

  await sudo('pfctl', '-E');
}
export async function stop() {
  await sudo('dnctl', '-q', 'flush');
  await sudo('dnctl', '-q', 'pipe', 'flush');
  await sudo('pfctl', '-f', '/etc/pf.conf');
  await sudo('pfctl', '-E');
  await sudo('pfctl', '-d');
}


================================================
FILE: lib/localHostTc.js
================================================
import sudo from './sudo.js';

export async function start(delay) {
  const halfWayDelay = delay / 2;

  try {
    await stop();
  } catch {
    // ignore
  }

  await sudo(
    'tc',
    'qdisc',
    'add',
    'dev',
    'lo',
    'root',
    'handle',
    '1:0',
    'netem',
    'delay',
    `${halfWayDelay}ms`
  );
}
export async function stop() {
  await sudo('tc', 'qdisc', 'del', 'dev', 'lo', 'root');
}


================================================
FILE: lib/pfctl.js
================================================
import sudo from './sudo.js';
import shell from './shell.js';

export async function start(up, down, rtt = 0, packetLoss = 0) {
  const halfWayRTT = rtt / 2;

  await stop();

  await sudo('dnctl', '-q', 'flush');
  await sudo('dnctl', '-q', 'pipe', 'flush');

  await sudo('dnctl', 'pipe', 1, 'config', 'delay', '0ms', 'noerror');
  await sudo('dnctl', 'pipe', 2, 'config', 'delay', '0ms', 'noerror');

  await shell(
    'echo "dummynet in inet from any to ! 127.0.0.1 pipe 1\n' +
      'dummynet out inet from ! 127.0.0.1 to any pipe 2\n' +
      'dummynet in inet6 from any to ! ::1 pipe 1\n' +
      'dummynet out inet6 from ! ::1 to any pipe 2" | sudo pfctl -f -'
  );

  if (down) {
    const parameters = [
      'dnctl',
      'pipe',
      1,
      'config',
      'bw',
      `${down}Kbit/s`,
      'delay',
      `${halfWayRTT}ms`
    ];
    if (packetLoss > 0) {
      parameters.push('plr', packetLoss / 100, 'noerror');
    }
    await sudo.apply(this, parameters);
  }

  if (up) {
    await sudo(
      'dnctl',
      'pipe',
      2,
      'config',
      'bw',
      `${up}Kbit/s`,
      'delay',
      `${halfWayRTT}ms`
    );
  }

  if (!up && !down && rtt > 0) {
    await sudo('dnctl', 'pipe', 1, 'config', 'delay', `${halfWayRTT}ms`);
    await sudo('dnctl', 'pipe', 2, 'config', 'delay', `${halfWayRTT}ms`);
  }

  await sudo('pfctl', '-E');
}
export async function stop() {
  await sudo('dnctl', '-q', 'flush');
  await sudo('dnctl', '-q', 'pipe', 'flush');
  await sudo('pfctl', '-f', '/etc/pf.conf');
  await sudo('pfctl', '-E');
  await sudo('pfctl', '-d');
}


================================================
FILE: lib/shell.js
================================================
import execFile from './execFile.js';

export default function shell(command) {
  if (process.env.LOG_THROTTLE) {
    console.log(command);
  }
  return execFile(command, { shell: true });
}


================================================
FILE: lib/sudo.js
================================================
import execFile from './execFile.js';

export default function sudo(command, ...arguments_) {
  if (process.env.LOG_THROTTLE) {
    console.log('sudo', command, ...arguments_);
  }
  return execFile('sudo', [command, ...arguments_]);
}


================================================
FILE: lib/tc.js
================================================
import shell from './shell.js';
import sudo from './sudo.js';

async function getDefaultInterface() {
  // Try the default route first
  const routeResult = await shell(
    "sudo ip route | awk '/default/ {print $5; exit}' | tr -d '\n'"
  );
  if (routeResult.stdout.length > 0) {
    return routeResult.stdout;
  }

  // Fall back to finding the interface with a global IP address,
  // since the default route may be gone while throttling is active
  const addrResult = await shell(
    "ip -o -4 addr show scope global | awk '{print $2; exit}'"
  );
  if (addrResult.stdout.trim().length > 0) {
    return addrResult.stdout.trim();
  }

  throw new Error('Could not find the default network interface');
}

async function moduleProbe() {
  try {
    await sudo('modprobe', 'ifb');
    // eslint-disable-next-line no-empty
  } catch {}
}
async function setupifb0() {
  try {
    // Check if ifb0 exist
    await sudo('ip', 'link', 'show', 'ifb0');
  } catch {
    // Add the interface
    await sudo('ip', 'link', 'add', 'ifb0', 'type', 'ifb');
  }

  // Bring the interface up
  await sudo('ip', 'link', 'set', 'ifb0', 'up');
}

async function setup(defaultInterface) {
  await sudo('tc', 'qdisc', 'add', 'dev', defaultInterface, 'ingress');
  await sudo(
    'tc',
    'filter',
    'add',
    'dev',
    defaultInterface,
    'parent',
    'ffff:',
    'protocol',
    'ip',
    'u32',
    'match',
    'u32',
    '0',
    '0',
    'flowid',
    '1:1',
    'action',
    'mirred',
    'egress',
    'redirect',
    'dev',
    'ifb0'
  );
  await sudo(
    'tc',
    'filter',
    'add',
    'dev',
    defaultInterface,
    'parent',
    'ffff:',
    'protocol',
    'ipv6',
    'u32',
    'match',
    'u32',
    '0',
    '0',
    'flowid',
    '1:1',
    'action',
    'mirred',
    'egress',
    'redirect',
    'dev',
    'ifb0'
  );
}

async function setLimits(up, down, halfWayRTT, packetLoss, indexFace) {
  if (down) {
    const parameters = [
      'tc',
      'qdisc',
      'add',
      'dev',
      'ifb0',
      'root',
      'handle',
      '1:0',
      'netem',
      'delay',
      `${halfWayRTT}ms`,
      'rate',
      `${down}kbit`
    ];

    if (packetLoss) {
      parameters.push('loss', `${packetLoss}%`);
    }

    await sudo.apply(this, parameters);
  }
  if (up) {
    const parameters = [
      'tc',
      'qdisc',
      'add',
      'dev',
      indexFace,
      'root',
      'handle',
      '1:0',
      'netem',
      'delay',
      `${halfWayRTT}ms`,
      'rate',
      `${up}kbit`
    ];

    if (packetLoss) {
      parameters.push('loss', `${packetLoss}%`);
    }

    await sudo.apply(this, parameters);
  }

  if (!up && !down && halfWayRTT > 0) {
    await sudo(
      'tc',
      'qdisc',
      'add',
      'dev',
      'ifb0',
      'root',
      'handle',
      '1:0',
      'netem',
      'delay',
      `${halfWayRTT}ms`
    );

    await sudo(
      'tc',
      'qdisc',
      'add',
      'dev',
      indexFace,
      'root',
      'handle',
      '1:0',
      'netem',
      'delay',
      `${halfWayRTT}ms`
    );
  }
}

export async function start(up, down, rtt = 0, packetLoss = 0) {
  const halfWayRTT = rtt / 2;

  try {
    await stop();
  } catch {
    // ignore
  }

  const indexFace = await getDefaultInterface();
  await moduleProbe();
  await setupifb0();
  await setup(indexFace);
  await setLimits(up, down, halfWayRTT, packetLoss, indexFace);
}
export async function stop() {
  const indexFace = await getDefaultInterface();

  try {
    try {
      await sudo('tc', 'qdisc', 'del', 'dev', indexFace, 'root');
      await sudo('tc', 'qdisc', 'del', 'dev', indexFace, 'ingress');
    } catch {
      // make sure we try to remove the ingress
      await sudo('tc', 'qdisc', 'del', 'dev', indexFace, 'ingress');
    }
  } catch {
    // ignore
  }

  try {
    await sudo('tc', 'qdisc', 'del', 'dev', 'ifb0', 'root');
  } catch {
    // do nada
  }
}


================================================
FILE: package.json
================================================
{
  "name": "@sitespeed.io/throttle",
  "version": "6.0.0",
  "description": "Throttle your connection",
  "type": "module",
  "exports": "./lib/index.js",
  "engines": {
    "node": ">=14.16"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/sitespeedio/throttle.git"
  },
  "keywords": [
    "latency",
    "throttle",
    "bandwith",
    "webperf",
    "perfmatters"
  ],
  "author": {
    "name": "Peter Hedenskog",
    "url": "https://www.peterhedenskog.com"
  },
  "contributors": [
    {
      "name": "Tobias Lidskog"
    },
    {
      "name": "Jonathan Lee"
    }
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/sitespeedio/throttle/issues"
  },
  "homepage": "https://www.sitespeed.io",
  "devDependencies": {
    "@eslint/js": "10.0.1",
    "eslint": "10.1.0",
    "eslint-plugin-prettier": "5.5.5",
    "eslint-plugin-unicorn": "63.0.0",
    "globals": "17.4.0",
    "prettier": "3.8.1"
  },
  "dependencies": {
    "minimist": "1.2.8"
  },
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  },
  "bin": "./bin/index.js",
  "main": "./lib/index.js"
}


================================================
FILE: release.sh
================================================
#!/bin/bash
set -e


# Super simple release script for sitespeed.io
# Lets use it it for now and make it better over time :)
# You need np for this to work
# npm install --global np
np $1  --any-branch 

# Update the docs with latest release number
bin/index.js --version  > ../sitespeed.io/docs/_includes/version/throttle.txt

================================================
FILE: test/Dockerfile
================================================
FROM sitespeedio/node:ubuntu-20.04-nodejs-16.5.0

RUN apt-get update && apt-get install libnss3-tools iproute2 sudo net-tools -y
RUN mkdir -p /usr/src/app

VOLUME /throttle
WORKDIR /usr/src/app

COPY package.json /usr/src/app/
COPY package-lock.json /usr/src/app/
RUN npm install --production
COPY . /usr/src/app

COPY test/start.sh /start.sh

ENTRYPOINT ["/start.sh"]


================================================
FILE: test/config.json
================================================
{
    "up": 330 ,
    "down": 200,
    "rtt": 1000
}

================================================
FILE: test/start.sh
================================================
#!/bin/bash
set -e

## Start/stop a couple of times
bin/index.js --profile 3gslow
bin/index.js --stop
bin/index.js --profile cable
bin/index.js --stop
Download .txt
gitextract_34mq2ed_/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── docker.yml
│       ├── install.yml
│       ├── linux.yml
│       └── osx.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bin/
│   └── index.js
├── eslint.config.js
├── lib/
│   ├── execFile.js
│   ├── index.js
│   ├── localHostPfctl.js
│   ├── localHostTc.js
│   ├── pfctl.js
│   ├── shell.js
│   ├── sudo.js
│   └── tc.js
├── package.json
├── release.sh
└── test/
    ├── Dockerfile
    ├── config.json
    └── start.sh
Download .txt
SYMBOL INDEX (20 symbols across 9 files)

FILE: bin/index.js
  function run (line 77) | async function run(argv) {

FILE: lib/execFile.js
  function shell (line 5) | function shell(command, options) {

FILE: lib/index.js
  function verify (line 13) | function verify(options) {
  function start (line 29) | async function start(options = {}) {
  function stop (line 56) | async function stop(options = {}) {

FILE: lib/localHostPfctl.js
  function start (line 4) | async function start(rtt) {
  function stop (line 28) | async function stop() {

FILE: lib/localHostTc.js
  function start (line 3) | async function start(delay) {
  function stop (line 26) | async function stop() {

FILE: lib/pfctl.js
  function start (line 4) | async function start(up, down, rtt = 0, packetLoss = 0) {
  function stop (line 59) | async function stop() {

FILE: lib/shell.js
  function shell (line 3) | function shell(command) {

FILE: lib/sudo.js
  function sudo (line 3) | function sudo(command, ...arguments_) {

FILE: lib/tc.js
  function getDefaultInterface (line 4) | async function getDefaultInterface() {
  function moduleProbe (line 25) | async function moduleProbe() {
  function setupifb0 (line 31) | async function setupifb0() {
  function setup (line 44) | async function setup(defaultInterface) {
  function setLimits (line 96) | async function setLimits(up, down, halfWayRTT, packetLoss, indexFace) {
  function start (line 175) | async function start(up, down, rtt = 0, packetLoss = 0) {
  function stop (line 190) | async function stop() {
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
  {
    "path": ".editorconfig",
    "chars": 231,
    "preview": "# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\nch"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 833,
    "preview": "name: Run Docker\non:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - main\njobs:\n  build:\n    runs-o"
  },
  {
    "path": ".github/workflows/install.yml",
    "chars": 745,
    "preview": "name: Install latest release\non:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - main\njobs:\n  build"
  },
  {
    "path": ".github/workflows/linux.yml",
    "chars": 1712,
    "preview": "name: Linux\non:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - main\njobs:\n  build:\n    runs-on: ub"
  },
  {
    "path": ".github/workflows/osx.yml",
    "chars": 1119,
    "preview": "name: OSX\non:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - main\njobs:\n  build:\n    runs-on: maco"
  },
  {
    "path": ".gitignore",
    "chars": 12,
    "preview": "node_modules"
  },
  {
    "path": ".npmignore",
    "chars": 18,
    "preview": "test/*\nrelease.sh\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7149,
    "preview": "# CHANGELOG - throttle\n\n# 6.0.0 - 2026-03-23\n### Added\n* Added IPv6 throttling support on macOS [#94](https://github.com"
  },
  {
    "path": "LICENSE",
    "chars": 1099,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Peter Hedenskog & Tobias Lidskog\n\nPermission is hereby granted, free of charge"
  },
  {
    "path": "README.md",
    "chars": 4561,
    "preview": "# Simulate slow network connections on Linux and Mac OS X\n\n[![Linux](https://github.com/sitespeedio/throttle/workflows/L"
  },
  {
    "path": "bin/index.js",
    "chars": 4318,
    "preview": "#!/usr/bin/env node\n\nimport { readFileSync } from 'node:fs';\nimport minimist from 'minimist';\nimport { stop, start } fro"
  },
  {
    "path": "eslint.config.js",
    "chars": 752,
    "preview": "import prettier from 'eslint-plugin-prettier';\nimport unicorn from 'eslint-plugin-unicorn';\nimport globals from 'globals"
  },
  {
    "path": "lib/execFile.js",
    "chars": 236,
    "preview": "import { promisify } from 'node:util';\nimport { execFile } from 'node:child_process';\nconst execFilePromisified = promis"
  },
  {
    "path": "lib/index.js",
    "chars": 1879,
    "preview": "import { platform } from 'node:os';\nimport { start as startPfctl, stop as stopPfctl } from './pfctl.js';\nimport { start "
  },
  {
    "path": "lib/localHostPfctl.js",
    "chars": 692,
    "preview": "import sudo from './sudo.js';\nimport shell from './shell.js';\n\nexport async function start(rtt) {\n  const halfWayRTT = r"
  },
  {
    "path": "lib/localHostTc.js",
    "chars": 413,
    "preview": "import sudo from './sudo.js';\n\nexport async function start(delay) {\n  const halfWayDelay = delay / 2;\n\n  try {\n    await"
  },
  {
    "path": "lib/pfctl.js",
    "chars": 1589,
    "preview": "import sudo from './sudo.js';\nimport shell from './shell.js';\n\nexport async function start(up, down, rtt = 0, packetLoss"
  },
  {
    "path": "lib/shell.js",
    "chars": 191,
    "preview": "import execFile from './execFile.js';\n\nexport default function shell(command) {\n  if (process.env.LOG_THROTTLE) {\n    co"
  },
  {
    "path": "lib/sudo.js",
    "chars": 236,
    "preview": "import execFile from './execFile.js';\n\nexport default function sudo(command, ...arguments_) {\n  if (process.env.LOG_THRO"
  },
  {
    "path": "lib/tc.js",
    "chars": 3922,
    "preview": "import shell from './shell.js';\nimport sudo from './sudo.js';\n\nasync function getDefaultInterface() {\n  // Try the defau"
  },
  {
    "path": "package.json",
    "chars": 1134,
    "preview": "{\n  \"name\": \"@sitespeed.io/throttle\",\n  \"version\": \"6.0.0\",\n  \"description\": \"Throttle your connection\",\n  \"type\": \"modu"
  },
  {
    "path": "release.sh",
    "chars": 326,
    "preview": "#!/bin/bash\nset -e\n\n\n# Super simple release script for sitespeed.io\n# Lets use it it for now and make it better over tim"
  },
  {
    "path": "test/Dockerfile",
    "chars": 369,
    "preview": "FROM sitespeedio/node:ubuntu-20.04-nodejs-16.5.0\n\nRUN apt-get update && apt-get install libnss3-tools iproute2 sudo net-"
  },
  {
    "path": "test/config.json",
    "chars": 52,
    "preview": "{\n    \"up\": 330 ,\n    \"down\": 200,\n    \"rtt\": 1000\n}"
  },
  {
    "path": "test/start.sh",
    "chars": 150,
    "preview": "#!/bin/bash\nset -e\n\n## Start/stop a couple of times\nbin/index.js --profile 3gslow\nbin/index.js --stop\nbin/index.js --pro"
  }
]

About this extraction

This page contains the full source code of the sitespeedio/throttle GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (32.9 KB), approximately 10.4k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!