This example takes all public IP addresses and passes them to the IP intelligence service, recording the average geographic area in square kilometers, the number of polygons that form the area, and the equivalent radius if the area can be represented as a circle.
This example takes all public IP addresses and passes them to the IP intelligence service, recording the average geographic area in square kilometers, the number of polygons that form the area, and the equivalent radius if the area can be represented as a circle.The example can take a long time to complete. The sample of IP addresses used in the metrics can be adjusted as a parameter. The example uses the LowMemory performance profile unless another, such as MaxPerformance, is named as the fourth command line argument. MaxPerformance runs much faster but holds the whole data file in memory.
This example is primarily designed for those who are interested in verifying the published metrics associated with 51Degrees' IP intelligence service.
This example requires an enterprise IP intelligence data file (.ipi). To obtain an enterprise data file for testing, please contact us.
const fs = require('fs');
const path = require('path');
const require51 = (requestedPackage) => {
try {
return require(path.join(__dirname, '/../../../node_modules/', requestedPackage));
} catch (e) {
return require(requestedPackage);
}
};
require51('fiftyone.ipintelligence.onpremise').IpIntelligenceOnPremisePipelineBuilder;
const ExampleUtils = require(path.join(__dirname,
'/../exampleUtils')).ExampleUtils;
const areas = require(path.join(__dirname, '/../areas'));
const LITE_V_4_1_IPI = '51Degrees-LiteV41.ipi';
const CONTACT_US = '/contact-us?utm_source=code&utm_medium=example&utm_campaign=51degrees-node&utm_content=fiftyone.ipintelligence.onpremise-examples-onpremise-metrics-console-metrics.js&utm_term=enterprise-data-file';
const DEFAULT_SAMPLE_PERCENTAGE = 0.1;
const LOG_INTERVAL_MS = 30000;
const KEY_PROPERTIES = [
'ContinentName',
'Country',
'LocationConfidence',
'ConnectionType',
'IsVPN',
'IsProxy',
'IsTor',
'IsPublicRouter'
];
const OUTPUT_FIELDS = KEY_PROPERTIES.concat([
'IpCount',
'AreaCount',
'AverageAreaKm',
'EquivalentRadiusKm',
'AveragePolygons'
]);
const ipToBytes = function (ip) {
if (ip.indexOf(':') === -1) {
return ip.split('.').map(function (part) {
return parseInt(part, 10);
});
}
const parts = ip.split('::');
let groups;
if (parts.length === 2) {
const head = parts[0] ? parts[0].split(':') : [];
const tail = parts[1] ? parts[1].split(':') : [];
groups = head
.concat(new Array(8 - head.length - tail.length).fill('0'))
.concat(tail);
} else {
groups = ip.split(':');
}
const bytes = [];
for (const group of groups) {
const value = parseInt(group, 16);
bytes.push((value >> 8) & 0xff);
bytes.push(value & 0xff);
}
return bytes;
};
const bytesToIp = function (bytes) {
if (bytes.length === 4) {
return bytes.join('.');
}
const groups = [];
for (let i = 0; i < bytes.length; i += 2) {
groups.push(((bytes[i] << 8) | bytes[i + 1]).toString(16));
}
return groups.join(':');
};
const tryGetNextAddress = function (bytes) {
for (let i = bytes.length - 1; i >= 0; i--) {
if (bytes[i] < 255) {
bytes[i]++;
return true;
}
bytes[i] = 0;
}
return false;
};
const ipEquals = function (a, b) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
};
const getKeyValue = function (ip, propertyName) {
let property;
try {
property = ip[propertyName.toLowerCase()];
} catch (e) {
return 'Unknown';
}
if (property === undefined || property === null) {
return 'Unknown';
}
if (property.hasValue === false) {
return property.noValueMessage;
}
const value =
ExampleUtils.
getSingleValue(ip, propertyName.toLowerCase());
return `${value}`;
};
const buildAreaIndex = function (metadata, progress) {
let areasProperty;
try {
areasProperty = metadata.getProperties().getByKey('Areas');
} catch (e) {
return undefined;
}
if (areasProperty === undefined || areasProperty === null) {
return undefined;
}
const wktAreas = new Map();
const values = metadata.getValuesForProperty(areasProperty);
let nextLog = Date.now() + LOG_INTERVAL_MS;
for (let i = 0; i < values.getSize(); i++) {
const value = values.getByIndex(i);
const wkt = value.getName();
wktAreas.set(wkt, areas.getAreas(wkt, 0, 0));
if (Date.now() >= nextLog) {
progress.write(`Mapped '${wktAreas.size}' areas\n`);
nextLog = Date.now() + LOG_INTERVAL_MS;
}
}
return wktAreas;
};
const validRanges = function * (metadata) {
const profiles = metadata.getProfiles();
for (let i = 0; i < profiles.getSize(); i++) {
const profile = profiles.getByIndex(i);
const component = metadata.getComponentForProfile(profile);
if (component.getName().toLowerCase() !== 'network') {
continue;
}
const values = metadata.getValuesForProfile(profile);
let start;
let end;
let registeredCountryUnknown = false;
for (let j = 0; j < values.getSize(); j++) {
const value = values.getByIndex(j);
const property = metadata.getPropertyForValue(value);
switch (property.getName()) {
case 'IpRangeStart':
start = value.getName();
break;
case 'IpRangeEnd':
end = value.getName();
break;
case 'RegisteredCountry':
if (value.getName() === 'Unknown') {
registeredCountryUnknown = true;
}
break;
}
}
if (start !== undefined &&
end !== undefined &&
registeredCountryUnknown === false) {
yield [start, end];
}
}
};
const processIp = async function (pipeline, wktAreas, groups, ipAddress) {
const flowData = pipeline.createFlowData();
flowData.evidence.add('query.client-ip', ipAddress);
await flowData.process();
const ip = flowData.ip;
const keyValues = KEY_PROPERTIES.map(function (propertyName) {
return getKeyValue(ip, propertyName);
});
const key = keyValues.join(' ');
let metric = groups.get(key);
if (metric === undefined) {
metric = {
keyValues,
ipCount: 0,
totalAreaKm: 0,
areaCount: 0,
polygons: new Map()
};
groups.set(key, metric);
}
metric.ipCount++;
if (wkt !== undefined && wktAreas.has(wkt)) {
const area = wktAreas.get(wkt);
metric.totalAreaKm += area.squareKms;
metric.polygons.set(
area.geometries,
(metric.polygons.get(area.geometries) || 0) + 1);
metric.areaCount++;
}
};
const writeToCsv = function (output, groups) {
output.write(OUTPUT_FIELDS.join(',') + '\n');
const metrics = Array.from(groups.values());
metrics.sort(function (a, b) {
return a.keyValues.join(' ') < b.keyValues.join(' ') ? -1 : 1;
});
for (const metric of metrics) {
const averageAreaKm = metric.areaCount > 0
? Math.floor(metric.totalAreaKm / metric.areaCount)
: 0;
const equivalentRadiusKm =
Math.floor(Math.sqrt(averageAreaKm / Math.PI));
let averagePolygons = 0;
if (metric.polygons.size > 0) {
let weightedSum = 0;
let total = 0;
metric.polygons.forEach(function (count, polygons) {
weightedSum += polygons * count;
total += count;
});
averagePolygons = weightedSum / total;
}
const values = metric.keyValues.map(function (value) {
return value.indexOf(',') !== -1 || value.indexOf('"') !== -1
? '"' + value.replace(/"/g, '""') + '"'
: value;
}).concat([
metric.ipCount,
metric.areaCount,
averageAreaKm,
equivalentRadiusKm,
averagePolygons.toFixed(2)
]);
output.write(values.join(',') + '\n');
}
};
const run = async function (
dataFile,
output,
samplePercentage,
condition,
progress,
performanceProfile = 'LowMemory') {
// In this example, we use the IpIntelligenceOnPremisePipelineBuilder
// and configure it in code. For more information about
// pipelines in general see the documentation at
// /documentation/_pipeline_api__concepts__configuration__builders__index.html?utm_source=code&utm_medium=example&utm_campaign=51degrees-node&utm_content=fiftyone.ipintelligence.onpremise-examples-onpremise-metrics-console-metrics.js&utm_term=run
const pipeline = new IpIntelligenceOnPremisePipelineBuilder({
dataFile,
// The low memory profile is the default because this example needs
// the Enterprise data file, which is about 6 GB. It reads the file as
// it goes, which uses little memory but runs much more slowly.
// MaxPerformance holds all of the file in memory and runs much faster,
// where the machine has the memory for it. Choose it with the fourth
// command line argument. See the documentation for more detail on
// this and other configuration options:
// /documentation/_pipeline_api__features__automatic_datafile_updates.html?utm_source=code&utm_medium=example&utm_campaign=51degrees-node&utm_content=fiftyone.ipintelligence.onpremise-examples-onpremise-metrics-console-metrics.js&utm_term=run
// /documentation/_pipeline_api__features__usage_sharing.html?utm_source=code&utm_medium=example&utm_campaign=51degrees-node&utm_content=fiftyone.ipintelligence.onpremise-examples-onpremise-metrics-console-metrics.js&utm_term=run
performanceProfile,
// inhibit sharing usage for this example, usually this should be
// set 'true'
shareUsage: false,
// inhibit auto-update of the data file for this test
autoUpdate: false,
updateOnStart: false,
fileSystemWatcher: false
}).build();
const engine = pipeline.getElement('ip');
const metadata = engine.engine.getMetaData();
// The metrics are measured from the areas, which only the Enterprise
// data file has, so with a free data file the example says so and stops.
if (ExampleUtils.lacksEnterpriseProperties(engine, 'Measuring the areas',
['Areas'], progress, CONTACT_US)) {
return false;
}
// Create the data set structure to rapidly retrieve the data needed
// to calculate the metrics.
progress.write('Creating area index\n');
const wktAreas = buildAreaIndex(metadata, progress);
if (wktAreas === undefined) {
progress.write('The data file does not contain the \'Areas\' ' +
'property. An enterprise IP intelligence data file is required ' +
'for this example. To obtain an enterprise data file for ' +
'testing, please contact us: /contact-us?utm_source=code&utm_medium=example&utm_campaign=51degrees-node&utm_content=fiftyone.ipintelligence.onpremise-examples-onpremise-metrics-console-metrics.js&utm_term=no-areas-property\n');
return;
}
progress.write(`Created index for '${wktAreas.size}' areas\n`);
const groups = new Map();
let ranges = 0;
let processed = 0;
let nextLog = Date.now() + LOG_INTERVAL_MS;
for (const range of validRanges(metadata)) {
if (condition !== undefined && condition(range) === false) {
continue;
}
const current = ipToBytes(range[0]);
const end = ipToBytes(range[1]);
while (ipEquals(current, end) === false) {
if (Math.random() <= samplePercentage) {
await processIp(pipeline, wktAreas, groups, bytesToIp(current));
processed++;
}
if (tryGetNextAddress(current) === false) {
break;
}
if (Date.now() >= nextLog) {
progress.write(`Processed '${ranges}' ranges and '${processed}' ` +
`IP addresses, most recent '${bytesToIp(current)}'\n`);
nextLog = Date.now() + LOG_INTERVAL_MS;
}
}
ranges++;
}
progress.write(`Finished processing '${ranges}' ranges and ` +
`'${processed}' IP addresses\n`);
writeToCsv(output, groups);
return true;
};
if (process.env.JEST_WORKER_ID === undefined) {
const args = process.argv.slice(2);
const dataFile = args.length > 0
? args[0]
: ExampleUtils.findDataFile(LITE_V_4_1_IPI);
const outputFile = args.length > 1 ? args[1] : 'metrics-output.csv';
const samplePercentage = args.length > 2
? parseFloat(args[2])
: DEFAULT_SAMPLE_PERCENTAGE;
const performanceProfile = args.length > 3 ? args[3] : 'LowMemory';
const condition = undefined;
if (dataFile !== undefined) {
const output = fs.createWriteStream(outputFile);
run(
dataFile,
output,
samplePercentage,
condition,
process.stdout,
performanceProfile).then(function (measured) {
output.end();
if (measured) {
console.log(`Output written to '${outputFile}'`);
}
});
} else {
console.error('Failed to find an IP intelligence data file. Make ' +
'sure the cxx/data submodule has been fetched by running ' +
'`git submodule update --init --recursive`. The full path to a data ' +
'file can be specified as a command line argument');
}
};
module.exports = {
run
};
Definition fiftyone.ipintelligence.onpremise/examples/onpremise/exampleUtils.js:203
Definition ipIntelligenceOnPremisePipelineBuilder.js:35