Comparing version 0.1.3 to 0.1.4
@@ -10,3 +10,2 @@ 'use strict'; | ||
// requires | ||
var WebSocketClient = require('websocket').client; | ||
var urlLib = require('url'); | ||
@@ -17,7 +16,6 @@ var http = require('http'); | ||
var timing = require('./timing.js'); | ||
var websocket = require('./websocket.js'); | ||
// globals | ||
var log = new Log('info'); | ||
var latency; | ||
var requests = 0; | ||
@@ -28,3 +26,2 @@ // constants | ||
concurrency: 1, | ||
requestsPerSecond: 1, | ||
}; | ||
@@ -35,2 +32,5 @@ | ||
* A client for an HTTP connection. | ||
* Operation is an object which has these attributes: | ||
* - latency: a variable to measure latency. | ||
* - running: if the operation is running or not. | ||
* Params is an object which must contain: | ||
@@ -42,9 +42,8 @@ * - url: destination URL. | ||
* - payload: contents of request (default empty). | ||
* - maxRequests: stop sending requests after this global limit is reached | ||
* (default unlimited). | ||
* - maxSeconds: stop receiving requests after this number of seconds | ||
* (default unlimited). | ||
* - maxRequests: stop sending requests after this global limit is reached. | ||
* - requestsPerSecond: limit requests per second to send. | ||
* - maxSeconds: stop receiving requests after this number of seconds. | ||
* - noAgent: if true, do not use connection keep-alive (default false). | ||
*/ | ||
function HttpClient(params) | ||
function HttpClient(operation, params) | ||
{ | ||
@@ -58,2 +57,3 @@ // self-reference | ||
var timer; | ||
var id; | ||
@@ -67,3 +67,3 @@ /** | ||
{ | ||
log.error('No requests per second selected'); | ||
self.makeRequest(); | ||
return; | ||
@@ -76,16 +76,23 @@ } | ||
/** | ||
* Stop the HTTP client. | ||
*/ | ||
self.stop = function() | ||
{ | ||
if (!timer) | ||
{ | ||
return; | ||
} | ||
timer.stop(); | ||
} | ||
/** | ||
* Make a single request to the server. | ||
*/ | ||
self.makeRequest = function(callback) | ||
self.makeRequest = function() | ||
{ | ||
requests += 1; | ||
if (requests > params.maxRequests) | ||
if (!operation.running) | ||
{ | ||
if (callback) | ||
{ | ||
callback(null); | ||
} | ||
return timer.stop(); | ||
return; | ||
} | ||
var id = latency.start(id); | ||
id = operation.latency.start(id); | ||
var options = urlLib.parse(params.url); | ||
@@ -120,18 +127,3 @@ if (params.noAgent) | ||
} | ||
var request = http.request(options, getConnect(id, function(error, result) | ||
{ | ||
if (error) | ||
{ | ||
log.error('Connection %s failed: %s', id, error); | ||
} | ||
else | ||
{ | ||
log.debug('Connection %s ended', id); | ||
} | ||
latency.end(id); | ||
if (callback) | ||
{ | ||
callback(error, result); | ||
} | ||
})); | ||
var request = http.request(options, getConnect(id, requestFinished)); | ||
if (message) | ||
@@ -149,2 +141,24 @@ { | ||
/** | ||
* One request has finished, go for the next. | ||
*/ | ||
function requestFinished(error, result) | ||
{ | ||
if (error) | ||
{ | ||
log.error('Connection %s failed: %s', id, error); | ||
} | ||
else | ||
{ | ||
log.debug('Connection %s ended', id); | ||
} | ||
operation.latency.end(id); | ||
var callback; | ||
if (!params.requestsPerSecond) | ||
{ | ||
callback = self.makeRequest; | ||
} | ||
operation.callback(error, result, callback); | ||
} | ||
/** | ||
* Get a function to connect the player. | ||
@@ -180,107 +194,2 @@ */ | ||
/** | ||
* A client that connects to a websocket. | ||
*/ | ||
function WebsocketClient(url, requestsPerSecond) | ||
{ | ||
// self-reference | ||
var self = this; | ||
// attributes | ||
var connection; | ||
var lastCall; | ||
/** | ||
* Start the websocket client. | ||
*/ | ||
self.start = function() | ||
{ | ||
var client = new WebSocketClient(); | ||
client.on('connectFailed', function(error) { | ||
error('Connect Error: ' + error.toString()); | ||
}); | ||
client.on('connect', connect); | ||
client.connect(url, []); | ||
log.debug('WebSocket client connected to ' + url); | ||
} | ||
/** | ||
* Connect the player. | ||
*/ | ||
function connect(localConnection) | ||
{ | ||
connection = localConnection; | ||
connection.on('error', function(error) { | ||
error("Connection error: " + error.toString()); | ||
}); | ||
connection.on('close', function() { | ||
log.debug('Connection closed'); | ||
}); | ||
connection.on('message', function(message) { | ||
if (message.type != 'utf8') | ||
{ | ||
error('Invalid message type ' + message.type); | ||
return; | ||
} | ||
if (lastCall) | ||
{ | ||
var newCall = new Date().getTime(); | ||
latency.add(newCall - lastCall); | ||
entry += ', latency: ' + (newCall - lastCall); | ||
lastCall = null; | ||
} | ||
var json; | ||
try | ||
{ | ||
json = JSON.parse(message.utf8Data); | ||
} | ||
catch(e) | ||
{ | ||
error('Invalid JSON: ' + message.utf8Data); | ||
return; | ||
} | ||
receive(json); | ||
}); | ||
} | ||
/** | ||
* Receive a message from the server. | ||
*/ | ||
function receive(message) | ||
{ | ||
if (!message || !message.type) | ||
{ | ||
error('Wrong message ' + JSON.stringify(message)); | ||
return; | ||
} | ||
if (message.type == 'start') | ||
{ | ||
log.debug('Starting game for ' + self.playerId); | ||
setInterval(requestUpdate, Math.round(1000 / requestsPerSecond)); | ||
return; | ||
} | ||
if (message.requestId) | ||
{ | ||
latency.end(message.requestId); | ||
} | ||
} | ||
/** | ||
* Request an update from the server. | ||
*/ | ||
function requestUpdate() | ||
{ | ||
if (connection.connected) | ||
{ | ||
var update = { | ||
requestId: Math.floor(Math.random() * 0x100000000).toString(16), | ||
type: 'ping', | ||
}; | ||
connection.sendUTF(JSON.stringify(update)); | ||
latency.start(update.requestId); | ||
} | ||
} | ||
} | ||
/** | ||
* Run a load test. | ||
@@ -306,13 +215,7 @@ * Options is an object which may have: | ||
{ | ||
constructor = function(params) | ||
{ | ||
return new WebsocketClient(params.url, params.requestsPerSecond); | ||
}; | ||
constructor = websocket.WebSocketClient; | ||
} | ||
else if (options.url.startsWith('http')) | ||
{ | ||
constructor = function(params) | ||
{ | ||
return new HttpClient(params); | ||
}; | ||
constructor = HttpClient; | ||
} | ||
@@ -323,22 +226,91 @@ else | ||
} | ||
if (callback) | ||
{ | ||
options.callback = callback; | ||
} | ||
latency = new timing.Latency(options); | ||
startClients(options, constructor); | ||
var operation = new Operation(options, constructor, callback); | ||
operation.start(); | ||
} | ||
/** | ||
* Start a number of measuring clients. | ||
* A load test operation. | ||
*/ | ||
function startClients(options, constructor) | ||
var Operation = function(options, constructor, callback) | ||
{ | ||
for (var index = 0; index < options.concurrency; index++) | ||
// self-reference | ||
var self = this; | ||
// attributes | ||
self.running = true; | ||
self.latency = null; | ||
var clients = {}; | ||
var requests = 0; | ||
/** | ||
* Start the operation. | ||
*/ | ||
self.start = function() | ||
{ | ||
options.url = options.url.replaceAll('$index', index); | ||
var client = constructor(options); | ||
// start each client 100 ms after the last | ||
setTimeout(client.start, (index * 100) % 1000); | ||
self.latency = new timing.Latency(options); | ||
startClients(); | ||
if (options.maxSeconds) | ||
{ | ||
setTimeout(stop, options.maxSeconds * 1000); | ||
} | ||
} | ||
/** | ||
* Call after each operation has finished. | ||
*/ | ||
self.callback = function(error, result, next) | ||
{ | ||
requests += 1; | ||
if (options.maxRequests) | ||
{ | ||
if (requests == options.maxRequests) | ||
{ | ||
stop(); | ||
} | ||
if (requests > options.maxRequests) | ||
{ | ||
log.debug('Should have no more running clients'); | ||
} | ||
} | ||
if (self.running && next) | ||
{ | ||
next(); | ||
} | ||
} | ||
/** | ||
* Start a number of measuring clients. | ||
*/ | ||
function startClients() | ||
{ | ||
var url = options.url; | ||
for (var index = 0; index < options.concurrency; index++) | ||
{ | ||
if (options.indexParam) | ||
{ | ||
options.url = url.replaceAll(indexParam, index); | ||
} | ||
var client = new constructor(self, options); | ||
clients[index] = client; | ||
// start each client 100 ms after the last | ||
setTimeout(client.start, (index * 100) % 1000); | ||
} | ||
} | ||
/** | ||
* Stop clients. | ||
*/ | ||
function stop() | ||
{ | ||
self.running = false; | ||
if (callback) | ||
{ | ||
callback(); | ||
} | ||
for (var index in clients) | ||
{ | ||
clients[index].stop(); | ||
} | ||
} | ||
} | ||
@@ -353,6 +325,8 @@ | ||
console.log(' where URL can be a regular HTTP or websocket URL.'); | ||
console.log('Options are:'); | ||
console.log('Apache ab-compatible options are:'); | ||
console.log(' -n requests Number of requests to perform'); | ||
console.log(' -c concurrency Number of multiple requests to make'); | ||
console.log(' -t timelimit Seconds to max. wait for responses'); | ||
console.log(' -r Do not exit on socket receive errors'); | ||
console.log('Other options are:'); | ||
console.log(' --rps Requests per second for each client'); | ||
@@ -391,2 +365,5 @@ console.log(' --noagent Do not use http agent (default)'); | ||
return true; | ||
case '-r': | ||
options.recover = true; | ||
return true; | ||
case '--rps': | ||
@@ -393,0 +370,0 @@ options.requestsPerSecond = args.shift(); |
@@ -20,3 +20,3 @@ 'use strict'; | ||
// constants | ||
var PORT = 8000; | ||
var PORT = 10408; | ||
@@ -33,3 +33,2 @@ | ||
concurrency: 100, | ||
requestsPerSecond: 10, | ||
method: 'POST', | ||
@@ -36,0 +35,0 @@ payload: { |
@@ -21,3 +21,3 @@ 'use strict'; | ||
var headersDebuggedTime = Date.now(); | ||
var latency = new timing.Latency(); | ||
var latency = new timing.Latency({}); | ||
@@ -24,0 +24,0 @@ // constants |
@@ -22,7 +22,10 @@ 'use strict'; | ||
* Latency measurements. Options can be: | ||
* - seconds: how many seconds to measure before showing latency. | ||
* - maxRequests: how many requests to make, alternative to seconds. | ||
* - callback: function to call when there are measures. | ||
* - showSeconds: how many seconds to measure before showing latency. | ||
* - showRequests: how many requests to make, alternative to seconds. | ||
* - maxRequests: max number of requests to measure before stopping. | ||
* - maxSeconds: max seconds, alternative to max requests. | ||
* An optional callback(error, results) will be called with an error, | ||
* or the results after max is reached. | ||
*/ | ||
exports.Latency = function(options) | ||
exports.Latency = function(options, callback) | ||
{ | ||
@@ -34,26 +37,13 @@ // self-reference | ||
var requests = {}; | ||
var secondsMeasured = 5; | ||
var maxRequests = null; | ||
var showSeconds = options.showSeconds || 5; | ||
var partialRequests = 0; | ||
var partialTime = 0; | ||
var lastShown = microtime.now(); | ||
var initialTime = microtime.now(); | ||
var totalRequests = 0; | ||
var totalTime = 0; | ||
var lastShown = microtime.now(); | ||
var callback = null; | ||
var maxLatencyMs = 0; | ||
var histogramMs = {}; | ||
var running = true; | ||
// init | ||
if (options) | ||
{ | ||
if (options.maxRequests) | ||
{ | ||
maxRequests = options.maxRequests; | ||
} | ||
if (options.seconds) | ||
{ | ||
secondsMeasured = options.seconds; | ||
} | ||
if (options.callback) | ||
{ | ||
callback = options.callback; | ||
} | ||
} | ||
/** | ||
@@ -76,5 +66,9 @@ * Start the request with the given id. | ||
{ | ||
error('Message id ' + requestId + ' not found'); | ||
log.error('Message id ' + requestId + ' not found'); | ||
return; | ||
} | ||
if (!running) | ||
{ | ||
return; | ||
} | ||
add(microtime.now() - requests[requestId]); | ||
@@ -87,16 +81,25 @@ delete requests[requestId]; | ||
*/ | ||
function add(value) | ||
function add(time) | ||
{ | ||
if (!secondsMeasured) | ||
log.debug('New value: %s', time); | ||
partialTime += time; | ||
partialRequests++; | ||
totalTime += time; | ||
totalRequests++; | ||
log.debug('Partial requests: %s', partialRequests); | ||
var rounded = Math.floor(time / 1000); | ||
if (rounded > maxLatencyMs) | ||
{ | ||
error('Please call init() with seconds to measure'); | ||
return; | ||
maxLatencyMs = rounded; | ||
} | ||
log.debug('New value: %s', value); | ||
totalTime += value; | ||
totalRequests++; | ||
log.debug('Total requests: %s', totalRequests); | ||
if (!histogramMs[rounded]) | ||
{ | ||
log.debug('Initializing histogram for %s', rounded); | ||
histogramMs[rounded] = 0; | ||
} | ||
histogramMs[rounded] += 1; | ||
showPartial(); | ||
if (isFinished()) | ||
{ | ||
show(); | ||
finish(); | ||
} | ||
@@ -106,56 +109,132 @@ } | ||
/** | ||
* Show an error message, or send to the callback. | ||
* Check out if enough seconds have elapsed, or enough requests were received. | ||
* If so, show latency for partial requests. | ||
*/ | ||
function error(message) | ||
function showPartial() | ||
{ | ||
if (callback) | ||
if (options.showRequests && partialRequests < options.showRequests) | ||
{ | ||
callback(error, null); | ||
return; | ||
} | ||
log.error(message); | ||
var elapsedSeconds = (microtime.now() - lastShown) / 1000000; | ||
if (elapsedSeconds < showSeconds) | ||
{ | ||
return; | ||
} | ||
var meanTime = partialTime / partialRequests; | ||
var results = { | ||
meanLatencyMs: Math.round(meanTime / 10) / 100, | ||
rps: Math.round(partialRequests / elapsedSeconds), | ||
} | ||
log.info('Requests / second: %s, mean latency: %s ms', results.rps, results.meanLatencyMs); | ||
partialTime = 0; | ||
partialRequests = 0; | ||
if (elapsedSeconds > 2 * showSeconds) | ||
{ | ||
lastShown = microtime.now(); | ||
} | ||
else | ||
{ | ||
lastShown += showSeconds * 1000000; | ||
} | ||
} | ||
/** | ||
* Check out if enough seconds have elapsed, or enough requests were received. | ||
* Check out if the measures are finished. | ||
*/ | ||
function isFinished() | ||
{ | ||
if (maxRequests) | ||
log.debug('Total requests %s, max requests: %s', totalRequests, options.maxRequests); | ||
if (options.maxRequests && totalRequests >= options.maxRequests) | ||
{ | ||
return (totalRequests >= maxRequests); | ||
log.debug('Max requests reached: %s', totalRequests); | ||
return true; | ||
} | ||
var secondsElapsed = (microtime.now() - lastShown) / 1000000; | ||
return (secondsElapsed >= secondsMeasured); | ||
var elapsedSeconds = (microtime.now() - initialTime) / 1000000; | ||
if (options.maxSeconds && elapsedSeconds >= maxSeconds) | ||
{ | ||
log.debug('Max seconds reached: %s', totalRequests); | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Checks if enough seconds have elapsed, or enough requests received. | ||
* Show latency for finished requests, or send to the callback. | ||
* We are finished. | ||
*/ | ||
function show() | ||
function finish() | ||
{ | ||
var secondsElapsed = (microtime.now() - lastShown) / 1000000; | ||
var meanTime = totalTime / totalRequests; | ||
var results = { | ||
meanLatencyMs: Math.round(meanTime / 10) / 100, | ||
rps: Math.round(totalRequests / secondsElapsed), | ||
} | ||
running = false; | ||
if (callback) | ||
{ | ||
callback(null, results); | ||
return callback(null, self.getResults()); | ||
} | ||
else | ||
{ | ||
log.info('Requests / second: %s, mean latency: %s ms', results.rps, results.meanLatencyMs); | ||
self.show(); | ||
} | ||
/** | ||
* Get final results. | ||
*/ | ||
self.getResults = function() | ||
{ | ||
var elapsedSeconds = (microtime.now() - initialTime) / 1000000; | ||
var meanTime = totalTime / totalRequests; | ||
return { | ||
totalRequests: totalRequests, | ||
totalTimeSeconds: elapsedSeconds, | ||
rps: Math.round(partialRequests / elapsedSeconds), | ||
meanLatencyMs: Math.round(meanTime / 10) / 100, | ||
} | ||
totalTime = 0; | ||
totalRequests = 0; | ||
if (secondsElapsed > 2 * secondsMeasured) | ||
} | ||
/** | ||
* Compute the percentiles. | ||
*/ | ||
self.computePercentiles = function() | ||
{ | ||
log.debug('Histogram: %s', util.inspect(histogramMs)); | ||
var percentiles = { | ||
50: false, | ||
90: false, | ||
95: false, | ||
99: false, | ||
}; | ||
var counted = 0; | ||
for (var ms = 0; ms <= maxLatencyMs; ms++) | ||
{ | ||
lastShown = microtime.now(); | ||
if (!histogramMs[ms]) | ||
{ | ||
continue; | ||
} | ||
log.debug('Histogram for %s: %s', ms, histogramMs[ms]); | ||
counted += histogramMs[ms]; | ||
var percent = counted / totalRequests * 100; | ||
for (var percentile in percentiles) | ||
{ | ||
log.debug('Checking percentile %s for %s', percentile, percent); | ||
if (!percentiles[percentile] && percent > percentile) | ||
{ | ||
percentiles[percentile] = ms; | ||
} | ||
} | ||
} | ||
else | ||
return percentiles; | ||
} | ||
/** | ||
* Show final results. | ||
*/ | ||
self.show = function() | ||
{ | ||
var results = self.getResults(); | ||
log.info('Completed requests: %s', results.totalRequests); | ||
log.info('Total time: %s s', results.totalTimeSeconds); | ||
log.info('Requests / second: %s', results.rps); | ||
log.info('Mean latency: %s ms', results.meanLatencyMs); | ||
log.info(''); | ||
log.info('Percentage of the requests served within a certain time'); | ||
var percentiles = self.computePercentiles(); | ||
for (var percentile in percentiles) | ||
{ | ||
lastShown += secondsMeasured * 1000000; | ||
log.info(' %s% %s ms', percentile, percentiles[percentile]); | ||
} | ||
@@ -170,3 +249,3 @@ } | ||
{ | ||
var latency = new exports.Latency(); | ||
var latency = new exports.Latency({}); | ||
var firstId = latency.start(); | ||
@@ -187,5 +266,4 @@ testing.assert(firstId, 'Invalid first latency id', callback); | ||
maxRequests: 10, | ||
callback: callback, | ||
}; | ||
var latency = new exports.Latency(options); | ||
var latency = new exports.Latency(options, callback); | ||
for (var i = 0; i < 10; i++) | ||
@@ -199,2 +277,33 @@ { | ||
/** | ||
* Check that percentiles are correct. | ||
*/ | ||
function testLatencyPercentiles(callback) | ||
{ | ||
var options = { | ||
maxRequests: 10, | ||
}; | ||
var latency = new exports.Latency(options, function(error, result) | ||
{ | ||
var percentiles = latency.computePercentiles(); | ||
for (var percentile in percentiles) | ||
{ | ||
testing.assert(percentiles[percentile] !== false, 'Empty percentile for %s', percentile, callback); | ||
} | ||
testing.success(percentiles, callback); | ||
}); | ||
for (var ms = 1; ms <= 10; ms++) | ||
{ | ||
log.debug('Starting %s', ms); | ||
(function() { | ||
var id = latency.start(); | ||
setTimeout(function() | ||
{ | ||
log.debug('Ending %s', id); | ||
latency.end(id); | ||
}, ms); | ||
})(); | ||
} | ||
} | ||
/** | ||
* A high resolution timer. | ||
@@ -268,2 +377,3 @@ * Initialize with milliseconds to wait and the callback to call. | ||
latencyRequests: testLatencyRequests, | ||
latencyPercentiles: testLatencyPercentiles, | ||
timer: testTimer, | ||
@@ -270,0 +380,0 @@ }; |
{ | ||
"name": "loadtest", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"description": "Load test scripts.", | ||
@@ -5,0 +5,0 @@ "homepage": "http://milliearth.org/", |
@@ -7,7 +7,10 @@ # loadtest | ||
Just run: | ||
$ npm install loadtest | ||
Install globally as root: | ||
# npm install -g loadtest | ||
Or add package loadtest to your package.json dependencies. | ||
On Ubuntu or Mac OS X systems install using sudo: | ||
$ sudo install -g loadtest | ||
For programmatic access, install locally or add package loadtest to your package.json dependencies. | ||
## Usage | ||
@@ -17,7 +20,12 @@ | ||
$ node node_modules/lib/loadtest.js [URL] or [websocket URL] | ||
$ loadtest [-n requests] [-c concurrency] [URL] | ||
The URL can be "http://" or "ws://" for websockets. (Note: websockets are not working at the moment, patches welcome.) Set the desired level of concurrency with the -c parameter. | ||
Single-dash parameters (e.g. -n) are designed to be compatible with Apache's ab. | ||
http://httpd.apache.org/docs/2.2/programs/ab.html | ||
To get online help, run loadtest without parameters: | ||
$ node node_modules/lib/loadtest.js | ||
$ loadtest | ||
@@ -28,16 +36,24 @@ ### Advanced Usage | ||
$ node node_modules/lib/loadtest.js [-n requests] [-c concurrency] ... | ||
$ loadtest [-n requests] [-c concurrency] ... | ||
#### Concurrency | ||
#### -n requests | ||
Number of requests to send out. | ||
#### -c concurrency | ||
loadtest will create a simultaneous number of clients; this parameter controls how many. | ||
#### Requests Per Second | ||
#### -t timelimit | ||
Controls the number of requests per second for each client. | ||
Number of seconds to wait until requests no longer go out. (Note: this is different than Apache's ab, which stops _receiving_ requests after the given seconds.) | ||
#### --noagent | ||
#### --rps requestsPerSecond | ||
Open connections without keep-alive: send header 'Connection: Close' instead of 'Connection: Keep-alive'. | ||
Controls the number of requests per second for each client. (Note: this parameter is not present in Apache's ab.) | ||
#### --agent | ||
Open connections using keep-alive: send header 'Connection: Keep-alive' instead of 'Connection: Close'. (Warning: uses the default node.js agent, which means there is a limit in outgoing connections.) | ||
### Server | ||
@@ -47,3 +63,3 @@ | ||
$ node node_modules/lib/testserver.js [port] | ||
$ testserver [port] | ||
@@ -50,0 +66,0 @@ It will show the number of requests received per second, the latency in answering requests and the headers for selected requests. |
14
test.js
@@ -9,5 +9,2 @@ 'use strict'; | ||
// requires | ||
var prototypes = require('./lib/prototypes.js'); | ||
var timing = require('./lib/timing.js'); | ||
var sample = require('./lib/sample.js'); | ||
var util = require('util'); | ||
@@ -26,7 +23,8 @@ var testing = require('testing'); | ||
{ | ||
var tests = { | ||
prototypes: prototypes.test, | ||
timing: timing.test, | ||
sample: sample.test, | ||
}; | ||
var tests = {}; | ||
var libs = [ 'prototypes', 'timing', 'sample', 'websocket' ]; | ||
libs.forEach(function(lib) | ||
{ | ||
tests[lib] = require('./lib/' + lib + '.js').test; | ||
}); | ||
testing.run(tests, 2200, callback); | ||
@@ -33,0 +31,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34470
13
1248
133
3