diff --git a/binding/javascript/package.json b/binding/javascript/package.json index 5720739..f20762f 100644 --- a/binding/javascript/package.json +++ b/binding/javascript/package.json @@ -38,8 +38,9 @@ "homepage": "https://github.com/lionsoul2014/ip2region#readme", "devDependencies": { "@types/jest": "^30.0.0", + "argparse": "^2.0.1", "jest": "^30.2.0", "ts-jest": "^29.4.5", "typescript": "^5.9.3" } -} +} \ No newline at end of file diff --git a/binding/javascript/searcher.js b/binding/javascript/searcher.js index 9419825..75db934 100644 --- a/binding/javascript/searcher.js +++ b/binding/javascript/searcher.js @@ -15,6 +15,7 @@ import { export class Searcher { constructor(version, dbPath, vectorIndex, cBuffer) { this.ioCount = 0; + this.dbPath = dbPath; this.version = version; if (cBuffer != null) { this.handle = null; @@ -112,6 +113,13 @@ export class Searcher { fs.close(this.handle); } } + + toString() { + const vn = this.version.name; + const vi = this.vectorIndex == null ? 'null' : this.vectorIndex.length; + const cf = this.cBuffer == null ? 'null' : this.cBuffer.length; + return `{"version": ${vn}, "dbPath": ${this.dbPath}, "handle": ${this.handle}, "vectorIndex": ${vi} "cBuffer": ${cf}}`; + } } export function newWithFileOnly(version, dbPath) { diff --git a/binding/javascript/tests/bench.app.js b/binding/javascript/tests/bench.app.js new file mode 100644 index 0000000..2bbdcb3 --- /dev/null +++ b/binding/javascript/tests/bench.app.js @@ -0,0 +1,137 @@ +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. + +// app to do the xdb bench +// @Author Lion + +import * as xdb from '../index.js'; +import {ArgumentParser} from 'argparse'; +import readline from 'node:readline'; +import fs from 'fs'; + + +const parser = new ArgumentParser({ + add_help: true, + description: 'ip2region bench script', + prog: 'node tests/bench.app.js', + usage: 'Usage %(prog)s [command options]' +}); + +parser.add_argument('--db', {help: 'ip2region binary xdb file path'}); +parser.add_argument('--src', {help: 'source ip text file path'}); +parser.add_argument('--cache-policy', {help: 'cache policy: file/vectorIndex/content, default: vectorIndex'}); + +const args = parser.parse_args(); +const dbPath = args.db || ''; +const srcPath = args.src || ''; +const cachePolicy = args.cache_policy || 'vectorIndex'; + +// create the searcher +const createSearcher = () => { + const handle = fs.openSync(dbPath, 'r'); + + // verify the xdb file + // @Note: do NOT call it every time you create a searcher since this will slow + // down the search response. + // @see the verify function for details. + xdb.verify(handle); + + // get the ip version from the header + const version = xdb.versionFromHeader(xdb.loadHeader(handle)); + + let searcher = null; + switch(cachePolicy) { + case 'file': + searcher = xdb.newWithFileOnly(version, dbPath); + break; + case 'vectorIndex': + const vIndex = xdb.loadVectorIndexFromFile(dbPath); + searcher = xdb.newWithVectorIndex(version, dbPath, vIndex); + break; + case 'content': + const cBuffer = xdb.loadContentFromFile(dbPath); + searcher = xdb.newWithBuffer(version, cBuffer); + break; + default: + fs.closeSync(handle); + throw new Error(`invalid cache-policy '${cachePolicy}'`); + } + + fs.closeSync(handle); + return searcher; +} + +const _split = (line) => { + const ps = []; + const s1 = line.indexOf('|'); + if (s1 === -1) { + ps.push(line); + return ps; + } + + ps.push(line.substring(0, s1)); + const s2 = line.indexOf('|', s1 + 1); + if (s2 === -1) { + ps.push(line.substring(s1+1)); + return ps; + } + + ps.push(line.substring(s1 + 1, s2)); + ps.push(line.substring(s2 + 1)); + return ps; +} + +const getMs = () => { + const hrTime = process.hrtime(); + return hrTime[0] * 1000000 + hrTime[1] / 1000; +} + +// console.log(`dbPath=${dbPath}, src=${src}, cachePolicy=${cachePolicy}`); +const main = () => { + if (dbPath.length < 1 || srcPath.length < 1) { + parser.print_help(); + return; + } + + const searcher = createSearcher(); + console.log(`Searcher: ${searcher.toString()}`); + + let totalNs = 0, count = 0; + + // read the source line and do the search bench + const rl = readline.createInterface({ + input: fs.createReadStream(srcPath), + crlfDelay: Infinity + }); + rl.on('line', async (l) => { + const ps = _split(l); + const sTime = process.hrtime(); + const sip = xdb.parseIP(ps[0]); + const eip = xdb.parseIP(ps[1]); + if (xdb.ipCompare(sip, eip) > 0) { + throw new Error(`start ip(${ps[0]}) should not be greater than end ip(${ps[1]})`); + } + + const test_list = [sip, eip]; + for (let i = 0; i < test_list.length; i++) { + const region = await searcher.search(test_list[i]); + if (region != ps[2]) { + throw new Error(`failed to search(${xdb.ipToString(test_list[i])}) with (${region} != ${ps[2]})`); + } + count++; + } + totalNs += process.hrtime(sTime); + }).on('error', (err) => { + console.log(err); + process.exit(1); + }); + + process.on('exit', (code) => { + const tookSec = totalNs / 1e9; + const _eachUs = count == 0 ? 0 : totalNs / 1e3 / count; + console.log(`Bench finished, {cachePolicy: ${cachePolicy}, total: ${count}, took: ${tookSec}s, cost: ${_eachUs} μs/op}`); + }); +} + +main(); \ No newline at end of file diff --git a/binding/javascript/util.js b/binding/javascript/util.js index 41b3c8b..a8136bb 100644 --- a/binding/javascript/util.js +++ b/binding/javascript/util.js @@ -303,19 +303,17 @@ export function versionFromName(name) { } export function versionFromHeader(h) { - let v = h.version(); - // old structure with ONLY IPv4 supporting - if (v == XdbStructure20) { + if (h.version == XdbStructure20) { return IPv4; } // structure 3.0 with IPv6 supporting - if (v != XdbStructure30) { + if (h.version != XdbStructure30) { return null; } - let ipVer = h.ipVersion(); + let ipVer = h.ipVersion; if (ipVer == XdbIPv4Id) { return IPv4; } else if (ipVer == XdbIPv6Id) {