查询返回对象增加took(us)和ioCount

This commit is contained in:
Wu Jian Ping 2022-07-21 22:31:44 +08:00
parent 743bbd204b
commit 4fde2363fa
4 changed files with 46 additions and 10 deletions

View File

@ -66,6 +66,29 @@ try {
## 查询测试 ## 查询测试
可以通过 `java -jar ip2region-{version}.jar search` 命令来测试查询:
```shell
➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar search
java -jar ip2region-{version}.jar search [command options]
options:
--db string ip2region binary xdb file path
--cache-policy string cache policy: file/vectorIndex/content
```
例如:使用默认的 data/ip2region.xdb 文件进行查询测试:
```shell
➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar search --db=../../data/ip2region.xdb
ip2region xdb searcher test program, cachePolicy: vectorIndex
type 'quit' to exit
ip2region>> 1.2.3.4
{region: 美国|0|华盛顿|0|谷歌, ioCount: 7, took: 82 μs}
ip2region>>
```
输入 ip 即可进行查询测试,也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的查询效果。
## bench 测试 ## bench 测试
## 单元测试结果 ## 单元测试结果

View File

@ -15,6 +15,7 @@ const IP_REGEX = /((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]
const getStartEndPtr = Symbol('#getStartEndPtr') const getStartEndPtr = Symbol('#getStartEndPtr')
const getBuffer = Symbol('#getBuffer') const getBuffer = Symbol('#getBuffer')
const openFilePromise = Symbol('#openFilePromise') const openFilePromise = Symbol('#openFilePromise')
const NS_PER_SEC = 1e9
class Searcher { class Searcher {
constructor (dbFile, vectorIndex, buffer) { constructor (dbFile, vectorIndex, buffer) {
@ -27,7 +28,7 @@ class Searcher {
} }
} }
async [getStartEndPtr] (idx, fd) { async [getStartEndPtr] (idx, fd, ioStatus) {
if (this._vectorIndex) { if (this._vectorIndex) {
// 区域二分索引的开始地址 // 区域二分索引的开始地址
// idx 开始读取 4 个字节,用小端字节序解码得到一个整数 // idx 开始读取 4 个字节,用小端字节序解码得到一个整数
@ -37,20 +38,21 @@ class Searcher {
const ePtr = this._vectorIndex.readUInt32LE(idx + 4) const ePtr = this._vectorIndex.readUInt32LE(idx + 4)
return { sPtr, ePtr } return { sPtr, ePtr }
} else { } else {
const buf = await this[getBuffer](256 + idx, 8, fd) const buf = await this[getBuffer](256 + idx, 8, fd, ioStatus)
const sPtr = buf.readUInt32LE() const sPtr = buf.readUInt32LE()
const ePtr = buf.readUInt32LE(4) const ePtr = buf.readUInt32LE(4)
return { sPtr, ePtr } return { sPtr, ePtr }
} }
} }
async [getBuffer] (offset, length, fd) { async [getBuffer] (offset, length, fd, ioStatus) {
if (this._buffer) { if (this._buffer) {
return this._buffer.subarray(offset, offset + length) return this._buffer.subarray(offset, offset + length)
} else { } else {
// 从文件中读取 // 从文件中读取
const buf = Buffer.alloc(length) const buf = Buffer.alloc(length)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ioStatus.ioCount += 1
fs.read(fd, buf, 0, length, offset, (err) => { fs.read(fd, buf, 0, length, offset, (err) => {
if (err) { if (err) {
reject(err) reject(err)
@ -76,6 +78,11 @@ class Searcher {
} }
async search (ip) { async search (ip) {
const startTime = process.hrtime()
const ioStatus = {
ioCount: 0
}
if (!IP_REGEX.test(ip)) { if (!IP_REGEX.test(ip)) {
throw new Error(`IP: ${ip} is invalid`) throw new Error(`IP: ${ip} is invalid`)
} }
@ -104,7 +111,7 @@ class Searcher {
const idx = i0 * VectorIndexCols * VectorIndexSize + i1 * VectorIndexSize const idx = i0 * VectorIndexCols * VectorIndexSize + i1 * VectorIndexSize
// 区域二分索引的开始地址和结束地址 // 区域二分索引的开始地址和结束地址
const { sPtr, ePtr } = await this[getStartEndPtr](idx, fd) const { sPtr, ePtr } = await this[getStartEndPtr](idx, fd, ioStatus)
// 二分搜索低位 // 二分搜索低位
let l = 0 let l = 0
@ -126,7 +133,7 @@ class Searcher {
// 从 p 位置开始读取 SegmentIndexSize = 14 个字节到 buff // 从 p 位置开始读取 SegmentIndexSize = 14 个字节到 buff
// 得到一个完整的上述描述的二分索引项,不过为了减少不必要的操作 // 得到一个完整的上述描述的二分索引项,不过为了减少不必要的操作
// 我们是按需要解码,此处 buff 为 p 开始的 14 个 byte 的数据 // 我们是按需要解码,此处 buff 为 p 开始的 14 个 byte 的数据
const buff = await this[getBuffer](p, SegmentIndexSize, fd) const buff = await this[getBuffer](p, SegmentIndexSize, fd, ioStatus)
// 前面 4 个字节是起始 IP // 前面 4 个字节是起始 IP
const sip = buff.readUInt32LE(0) const sip = buff.readUInt32LE(0)
@ -149,7 +156,7 @@ class Searcher {
const dataLen = buff.readUInt16LE(8) const dataLen = buff.readUInt16LE(8)
// 10 ~ 13 的 4 个字节是地域数据的地址 // 10 ~ 13 的 4 个字节是地域数据的地址
const dataPtr = buff.readUInt32LE(10) const dataPtr = buff.readUInt32LE(10)
const data = await this[getBuffer](dataPtr, dataLen, fd) const data = await this[getBuffer](dataPtr, dataLen, fd, ioStatus)
result = data.toString('utf-8') result = data.toString('utf-8')
break break
} }
@ -163,7 +170,10 @@ class Searcher {
fs.close(fd) fs.close(fd)
} }
return result const diff = process.hrtime(startTime)
const took = (diff[0] * NS_PER_SEC + diff[1]) / 1e6
return { region: result, ioCount: ioStatus.ioCount, took }
} }
} }

View File

@ -14,16 +14,19 @@ const searcher3 = Searcher.newWithFileOnly(dbPath)
describe('ip2region', () => { describe('ip2region', () => {
it('#newWithFileOnly and search', async () => { it('#newWithFileOnly and search', async () => {
const d = await searcher3.search('218.4.167.70') const d = await searcher3.search('218.4.167.70')
expect(d).equal('中国|0|江苏省|苏州市|电信') console.log(d)
expect(d.region).equal('中国|0|江苏省|苏州市|电信')
}) })
it('#newWithVectorIndex and search', async () => { it('#newWithVectorIndex and search', async () => {
const d = await searcher2.search('218.4.167.70') const d = await searcher2.search('218.4.167.70')
expect(d).equal('中国|0|江苏省|苏州市|电信') console.log(d)
expect(d.region).equal('中国|0|江苏省|苏州市|电信')
}) })
it('#newWithBuffer and search', async () => { it('#newWithBuffer and search', async () => {
const d = await searcher1.search('218.4.167.70') const d = await searcher1.search('218.4.167.70')
expect(d).equal('中国|0|江苏省|苏州市|电信')
expect(d.region).equal('中国|0|江苏省|苏州市|电信')
}) })
}) })

View File