@vpalmisano/throttler 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +40 -0
- package/build/src/app.d.ts +1 -0
- package/build/src/app.js +134 -0
- package/build/src/app.js.map +1 -0
- package/build/src/config.d.ts +21 -0
- package/build/src/config.js +199 -0
- package/build/src/config.js.map +1 -0
- package/build/src/generate-config-docs.d.ts +1 -0
- package/build/src/generate-config-docs.js +43 -0
- package/build/src/generate-config-docs.js.map +1 -0
- package/build/src/index.d.ts +4 -0
- package/build/src/index.js +21 -0
- package/build/src/index.js.map +1 -0
- package/build/src/throttle.d.ts +85 -0
- package/build/src/throttle.js +385 -0
- package/build/src/throttle.js.map +1 -0
- package/build/src/utils.d.ts +57 -0
- package/build/src/utils.js +223 -0
- package/build/src/utils.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +78 -0
- package/src/app.ts +151 -0
- package/src/config.ts +179 -0
- package/src/generate-config-docs.ts +51 -0
- package/src/index.ts +4 -0
- package/src/throttle.ts +562 -0
- package/src/utils.ts +249 -0
- package/throttler.js +2 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import convict, { addFormats } from 'convict'
|
|
2
|
+
import { ipaddress, url } from 'convict-format-with-validator'
|
|
3
|
+
import { existsSync } from 'fs'
|
|
4
|
+
|
|
5
|
+
import { logger } from './utils'
|
|
6
|
+
const log = logger('throttler:config')
|
|
7
|
+
|
|
8
|
+
const float = {
|
|
9
|
+
name: 'float',
|
|
10
|
+
coerce: (v: string) => parseFloat(v),
|
|
11
|
+
validate: (v: number) => {
|
|
12
|
+
if (!Number.isFinite(v)) throw new Error(`Invalid float: ${v}`)
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const index = {
|
|
17
|
+
name: 'index',
|
|
18
|
+
coerce: (v: unknown) => v,
|
|
19
|
+
validate: (v: boolean | string | number) => {
|
|
20
|
+
if (typeof v === 'string') {
|
|
21
|
+
if (v === 'true' || v === 'false' || v === '') return
|
|
22
|
+
if (v.indexOf('-') !== -1) {
|
|
23
|
+
v.split('-').forEach(n => {
|
|
24
|
+
if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))
|
|
25
|
+
throw new Error(`Invalid index: ${n}`)
|
|
26
|
+
})
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
if (v.indexOf(',') !== -1) {
|
|
30
|
+
v.split(',').forEach(n => {
|
|
31
|
+
if (isNaN(parseInt(n)) || !isFinite(parseInt(n)))
|
|
32
|
+
throw new Error(`Invalid index: ${n}`)
|
|
33
|
+
})
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
if (isNaN(parseInt(v)) || !isFinite(parseInt(v)))
|
|
37
|
+
throw new Error(`Invalid index: ${v}`)
|
|
38
|
+
} else if (typeof v === 'number' || typeof v === 'boolean') {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Invalid index: ${v}`)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
addFormats({ ipaddress, url, float, index })
|
|
46
|
+
|
|
47
|
+
// config schema
|
|
48
|
+
const configSchema = convict({
|
|
49
|
+
runDuration: {
|
|
50
|
+
doc: `If greater than 0, the test will stop after the provided number of \
|
|
51
|
+
seconds.`,
|
|
52
|
+
format: 'nat',
|
|
53
|
+
default: 0,
|
|
54
|
+
env: 'RUN_DURATION',
|
|
55
|
+
arg: 'run-duration',
|
|
56
|
+
},
|
|
57
|
+
throttleConfig: {
|
|
58
|
+
doc: `A JSON5 string with a valid network throttling configuration. \
|
|
59
|
+
Example: \
|
|
60
|
+
|
|
61
|
+
\`\`\`javascript
|
|
62
|
+
[{
|
|
63
|
+
sessions: '0-1',
|
|
64
|
+
device: 'eth0',
|
|
65
|
+
protocol: 'udp',
|
|
66
|
+
capture: 'capture.pcap',
|
|
67
|
+
up: {
|
|
68
|
+
rate: 1000,
|
|
69
|
+
delay: 50,
|
|
70
|
+
loss: 5,
|
|
71
|
+
queue: 10,
|
|
72
|
+
},
|
|
73
|
+
down: [
|
|
74
|
+
{ rate: 2000, delay: 50, loss: 2, queue: 20 },
|
|
75
|
+
{ rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },
|
|
76
|
+
]
|
|
77
|
+
}]
|
|
78
|
+
\`\`\`
|
|
79
|
+
The sessions field represents the sessions IDs range that will be affected by the rule, e.g.: "0-10", "2,4" or simply "2". \
|
|
80
|
+
The device, protocol, up, down fields are optional. When device is not set, the default route device will be used. If protocol is specified ('udp' or 'tcp'), \
|
|
81
|
+
only the packets with the specified protocol will be affected by the shaping rules. \
|
|
82
|
+
\
|
|
83
|
+
`,
|
|
84
|
+
format: String,
|
|
85
|
+
nullable: true,
|
|
86
|
+
default: '',
|
|
87
|
+
env: 'THROTTLE_CONFIG',
|
|
88
|
+
arg: 'throttle-config',
|
|
89
|
+
},
|
|
90
|
+
commandConfig: {
|
|
91
|
+
doc: `The commands configuration.\
|
|
92
|
+
Example: \
|
|
93
|
+
|
|
94
|
+
\`\`\`javascript
|
|
95
|
+
[{
|
|
96
|
+
session: 0,
|
|
97
|
+
command: "firefox https://www.speedtest.net",
|
|
98
|
+
}]
|
|
99
|
+
\`\`\`
|
|
100
|
+
`,
|
|
101
|
+
format: String,
|
|
102
|
+
nullable: true,
|
|
103
|
+
default: '',
|
|
104
|
+
env: 'COMMAND_CONFIG',
|
|
105
|
+
arg: 'command-config',
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
type ConfigDocs = Record<
|
|
110
|
+
string,
|
|
111
|
+
{ doc: string; format: string; default: string }
|
|
112
|
+
>
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Formats the schema documentation, calling the same function recursively.
|
|
116
|
+
* @param docs the documentation object to extend
|
|
117
|
+
* @param property the root property
|
|
118
|
+
* @param schema the config schema fragment
|
|
119
|
+
* @return the documentation object
|
|
120
|
+
*/
|
|
121
|
+
function formatDocs(
|
|
122
|
+
docs: ConfigDocs,
|
|
123
|
+
property: string | null,
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
125
|
+
schema: any,
|
|
126
|
+
): ConfigDocs {
|
|
127
|
+
if (schema._cvtProperties) {
|
|
128
|
+
Object.entries(schema._cvtProperties).forEach(([name, value]) => {
|
|
129
|
+
formatDocs(docs, `${property ? `${property}.` : ''}${name}`, value)
|
|
130
|
+
})
|
|
131
|
+
return docs
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (property) {
|
|
135
|
+
docs[property] =
|
|
136
|
+
// eslint-disable-line no-param-reassign
|
|
137
|
+
{
|
|
138
|
+
doc: schema.doc,
|
|
139
|
+
format: JSON.stringify(schema.format, null, 2),
|
|
140
|
+
default: JSON.stringify(schema.default, null, 2),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return docs
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* It returns the formatted configuration docs.
|
|
148
|
+
*/
|
|
149
|
+
export function getConfigDocs(): ConfigDocs {
|
|
150
|
+
return formatDocs({}, null, configSchema.getSchema())
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const schemaProperties = configSchema.getProperties()
|
|
154
|
+
|
|
155
|
+
/** [[include:config.md]] */
|
|
156
|
+
export type Config = typeof schemaProperties
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Loads the config object.
|
|
160
|
+
*/
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
export function loadConfig(filePath?: string, values?: any): Config {
|
|
163
|
+
if (filePath && existsSync(filePath)) {
|
|
164
|
+
log.debug(`Loading config from ${filePath}`)
|
|
165
|
+
configSchema.loadFile(filePath)
|
|
166
|
+
} else if (values) {
|
|
167
|
+
log.debug('Loading config from values.')
|
|
168
|
+
configSchema.load(values)
|
|
169
|
+
} else {
|
|
170
|
+
log.debug('Using default values.')
|
|
171
|
+
configSchema.load({})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
configSchema.validate({ allowed: 'strict' })
|
|
175
|
+
const config = configSchema.getProperties()
|
|
176
|
+
|
|
177
|
+
log.debug('Using config:', config)
|
|
178
|
+
return config
|
|
179
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { writeFile } from 'fs/promises'
|
|
2
|
+
|
|
3
|
+
import { getConfigDocs } from './config'
|
|
4
|
+
|
|
5
|
+
function formatJson(data: string): string {
|
|
6
|
+
return `\`${data.replace(/\n/g, '')}\``
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let data = `
|
|
10
|
+
The configuration properties are applied in the following order (from higher to
|
|
11
|
+
lower precedence):
|
|
12
|
+
|
|
13
|
+
- arguments passed to the executable in kebab case (e.g. \`--run-duration\`);
|
|
14
|
+
- environment variables in uppercase snake format (e.g. \`RUN_DURATION\`);
|
|
15
|
+
- \`config.json\` configuration file;
|
|
16
|
+
- default values.
|
|
17
|
+
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
const configDocs = getConfigDocs()
|
|
21
|
+
Object.entries(configDocs).forEach(entry => {
|
|
22
|
+
const [name, value] = entry
|
|
23
|
+
data += `\
|
|
24
|
+
## ${name}
|
|
25
|
+
${value.doc}
|
|
26
|
+
|
|
27
|
+
*Type*: \`${
|
|
28
|
+
value.format === '"nat"'
|
|
29
|
+
? 'positive int'
|
|
30
|
+
: value.format.replace(/^"(.+)"$/, '$1')
|
|
31
|
+
}\`
|
|
32
|
+
|
|
33
|
+
*Default*: ${formatJson(value.default)}
|
|
34
|
+
|
|
35
|
+
`
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
data += `
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
writeFile('docs/config.md', data).then(
|
|
45
|
+
() => {
|
|
46
|
+
console.log('done'); // eslint-disable-line
|
|
47
|
+
},
|
|
48
|
+
err => {
|
|
49
|
+
console.error(`Error writing file: ${err.message}`); // eslint-disable-line
|
|
50
|
+
},
|
|
51
|
+
)
|
package/src/index.ts
ADDED