254 lines
8.4 KiB
TypeScript
254 lines
8.4 KiB
TypeScript
/**
|
||
* S3存储桶速度测试脚本
|
||
* 测试内网和外网访问速度
|
||
*/
|
||
|
||
import { S3Client, GetObjectCommand, PutObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3'
|
||
import { randomBytes } from 'crypto'
|
||
|
||
// 从环境变量读取配置
|
||
const S3_ENDPOINT = process.env.S3_ENDPOINT || ''
|
||
const S3_BUCKET = process.env.S3_BUCKET || ''
|
||
const S3_PUBLIC_DOMAIN = process.env.S3_PUBLIC_DOMAIN || ''
|
||
const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || ''
|
||
const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY || ''
|
||
const SERVER_IP = process.env.SERVER_IP || ''
|
||
const MINIO_API_PORT = process.env.MINIO_API_PORT || '9000'
|
||
|
||
console.log('='.repeat(60))
|
||
console.log('S3存储桶速度测试')
|
||
console.log('='.repeat(60))
|
||
console.log(`S3_ENDPOINT: ${S3_ENDPOINT}`)
|
||
console.log(`S3_BUCKET: ${S3_BUCKET}`)
|
||
console.log(`S3_PUBLIC_DOMAIN: ${S3_PUBLIC_DOMAIN}`)
|
||
console.log(`SERVER_IP: ${SERVER_IP}`)
|
||
console.log(`MINIO_API_PORT: ${MINIO_API_PORT}`)
|
||
console.log('='.repeat(60))
|
||
console.log('')
|
||
|
||
// 创建S3客户端
|
||
const s3Client = new S3Client({
|
||
endpoint: S3_ENDPOINT,
|
||
region: process.env.S3_REGION || 'us-east-1',
|
||
credentials: {
|
||
accessKeyId: S3_ACCESS_KEY_ID,
|
||
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
||
},
|
||
forcePathStyle: true,
|
||
requestHandler: {
|
||
requestTimeout: 30000,
|
||
connectionTimeout: 10000,
|
||
},
|
||
})
|
||
|
||
// 测试连接速度
|
||
async function testConnectionSpeed(): Promise<number> {
|
||
console.log('📡 测试S3连接速度...')
|
||
const startTime = Date.now()
|
||
try {
|
||
const command = new ListObjectsV2Command({
|
||
Bucket: S3_BUCKET,
|
||
MaxKeys: 1,
|
||
})
|
||
await s3Client.send(command)
|
||
const duration = Date.now() - startTime
|
||
console.log(`✅ 连接成功,耗时: ${duration}ms`)
|
||
return duration
|
||
} catch (error: any) {
|
||
const duration = Date.now() - startTime
|
||
console.log(`⚠️ 连接测试完成(可能失败),耗时: ${duration}ms`)
|
||
if (error.message) {
|
||
console.log(` 错误: ${error.message}`)
|
||
}
|
||
return duration
|
||
}
|
||
}
|
||
|
||
// 测试上传速度
|
||
async function testUploadSpeed(sizeKB: number = 100): Promise<{ duration: number; speed: number }> {
|
||
console.log(`\n📤 测试上传速度(${sizeKB}KB文件)...`)
|
||
const testData = randomBytes(sizeKB * 1024)
|
||
const testKey = `speed-test-${Date.now()}.bin`
|
||
|
||
const startTime = Date.now()
|
||
try {
|
||
const command = new PutObjectCommand({
|
||
Bucket: S3_BUCKET,
|
||
Key: testKey,
|
||
Body: testData,
|
||
ContentType: 'application/octet-stream',
|
||
})
|
||
await s3Client.send(command)
|
||
const duration = Date.now() - startTime
|
||
const speed = (sizeKB / duration) * 1000 // KB/s
|
||
console.log(`✅ 上传成功,耗时: ${duration}ms,速度: ${speed.toFixed(2)} KB/s (${(speed / 1024).toFixed(2)} MB/s)`)
|
||
|
||
// 清理测试文件
|
||
try {
|
||
const deleteCommand = new PutObjectCommand({
|
||
Bucket: S3_BUCKET,
|
||
Key: testKey,
|
||
Body: Buffer.from(''),
|
||
})
|
||
await s3Client.send(deleteCommand).catch(() => {})
|
||
} catch {}
|
||
|
||
return { duration, speed }
|
||
} catch (error: any) {
|
||
const duration = Date.now() - startTime
|
||
console.log(`❌ 上传失败,耗时: ${duration}ms`)
|
||
if (error.message) {
|
||
console.log(` 错误: ${error.message}`)
|
||
}
|
||
return { duration, speed: 0 }
|
||
}
|
||
}
|
||
|
||
// 测试下载速度(通过HTTP)
|
||
async function testDownloadSpeed(url: string, label: string): Promise<{ duration: number; speed: number; ttfb: number }> {
|
||
console.log(`\n📥 测试下载速度(${label})...`)
|
||
console.log(` URL: ${url}`)
|
||
|
||
const startTime = Date.now()
|
||
let ttfb = 0
|
||
try {
|
||
const response = await fetch(url, {
|
||
method: 'HEAD', // 只获取头信息,不下载内容
|
||
signal: AbortSignal.timeout(10000), // 10秒超时
|
||
})
|
||
ttfb = Date.now() - startTime
|
||
|
||
if (response.ok) {
|
||
const contentLength = response.headers.get('content-length')
|
||
if (contentLength) {
|
||
const sizeKB = parseInt(contentLength) / 1024
|
||
console.log(`✅ HEAD请求成功`)
|
||
console.log(` TTFB (首字节时间): ${ttfb}ms`)
|
||
console.log(` 文件大小: ${sizeKB.toFixed(2)} KB`)
|
||
|
||
// 如果文件较小,尝试完整下载测试
|
||
if (sizeKB < 1000) {
|
||
const downloadStart = Date.now()
|
||
const downloadResponse = await fetch(url, {
|
||
signal: AbortSignal.timeout(30000),
|
||
})
|
||
if (downloadResponse.ok) {
|
||
await downloadResponse.arrayBuffer()
|
||
const downloadDuration = Date.now() - downloadStart
|
||
const speed = (sizeKB / downloadDuration) * 1000 // KB/s
|
||
console.log(` 完整下载耗时: ${downloadDuration}ms`)
|
||
console.log(` 下载速度: ${speed.toFixed(2)} KB/s (${(speed / 1024).toFixed(2)} MB/s)`)
|
||
return { duration: downloadDuration, speed, ttfb }
|
||
}
|
||
}
|
||
}
|
||
return { duration: ttfb, speed: 0, ttfb }
|
||
} else {
|
||
console.log(`⚠️ HTTP状态: ${response.status}`)
|
||
return { duration: ttfb, speed: 0, ttfb }
|
||
}
|
||
} catch (error: any) {
|
||
const duration = Date.now() - startTime
|
||
console.log(`❌ 下载测试失败,耗时: ${duration}ms`)
|
||
if (error.message) {
|
||
console.log(` 错误: ${error.message}`)
|
||
}
|
||
return { duration, speed: 0, ttfb: duration }
|
||
}
|
||
}
|
||
|
||
// 获取一个真实的文件URL用于测试
|
||
async function getTestFileUrl(): Promise<string | null> {
|
||
try {
|
||
const command = new ListObjectsV2Command({
|
||
Bucket: S3_BUCKET,
|
||
MaxKeys: 10,
|
||
})
|
||
const response = await s3Client.send(command)
|
||
if (response.Contents && response.Contents.length > 0) {
|
||
const testFile = response.Contents[0]
|
||
const key = testFile.Key || ''
|
||
if (key) {
|
||
return `${S3_PUBLIC_DOMAIN}/${key}`
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('无法获取测试文件URL:', error)
|
||
}
|
||
return null
|
||
}
|
||
|
||
// 主测试函数
|
||
async function runTests() {
|
||
console.log('开始测试...\n')
|
||
|
||
// 1. 测试连接速度
|
||
const connectionTime = await testConnectionSpeed()
|
||
|
||
// 2. 测试上传速度(小文件)
|
||
const uploadSmall = await testUploadSpeed(100)
|
||
|
||
// 3. 测试上传速度(大文件)
|
||
const uploadLarge = await testUploadSpeed(1024) // 1MB
|
||
|
||
// 4. 测试公网域名下载速度
|
||
const testFileUrl = await getTestFileUrl()
|
||
let publicDownload = { duration: 0, speed: 0, ttfb: 0 }
|
||
if (testFileUrl) {
|
||
publicDownload = await testDownloadSpeed(testFileUrl, '公网域名')
|
||
} else {
|
||
console.log('\n⚠️ 无法获取测试文件URL,跳过下载测试')
|
||
}
|
||
|
||
// 5. 测试内网地址下载速度(如果配置了内网IP)
|
||
let intranetDownload = { duration: 0, speed: 0, ttfb: 0 }
|
||
if (SERVER_IP && testFileUrl) {
|
||
try {
|
||
// 将公网域名URL转换为内网地址
|
||
const url = new URL(testFileUrl)
|
||
const intranetUrl = `http://${SERVER_IP}:${MINIO_API_PORT}${url.pathname}`
|
||
intranetDownload = await testDownloadSpeed(intranetUrl, '内网地址')
|
||
} catch (error) {
|
||
console.log('\n⚠️ 无法测试内网地址下载速度')
|
||
}
|
||
}
|
||
|
||
// 输出测试报告
|
||
console.log('\n' + '='.repeat(60))
|
||
console.log('测试报告')
|
||
console.log('='.repeat(60))
|
||
console.log(`连接速度: ${connectionTime}ms`)
|
||
console.log(`\n上传速度:`)
|
||
console.log(` 小文件(100KB): ${uploadSmall.speed.toFixed(2)} KB/s (${(uploadSmall.speed / 1024).toFixed(2)} MB/s)`)
|
||
console.log(` 大文件(1MB): ${uploadLarge.speed.toFixed(2)} KB/s (${(uploadLarge.speed / 1024).toFixed(2)} MB/s)`)
|
||
console.log(`\n下载速度:`)
|
||
if (publicDownload.ttfb > 0) {
|
||
console.log(` 公网域名 TTFB: ${publicDownload.ttfb}ms`)
|
||
if (publicDownload.speed > 0) {
|
||
console.log(` 公网域名速度: ${publicDownload.speed.toFixed(2)} KB/s (${(publicDownload.speed / 1024).toFixed(2)} MB/s)`)
|
||
}
|
||
}
|
||
if (intranetDownload.ttfb > 0) {
|
||
console.log(` 内网地址 TTFB: ${intranetDownload.ttfb}ms`)
|
||
if (intranetDownload.speed > 0) {
|
||
console.log(` 内网地址速度: ${intranetDownload.speed.toFixed(2)} KB/s (${(intranetDownload.speed / 1024).toFixed(2)} MB/s)`)
|
||
}
|
||
if (publicDownload.speed > 0 && intranetDownload.speed > 0) {
|
||
const speedup = (intranetDownload.speed / publicDownload.speed).toFixed(2)
|
||
console.log(`\n🚀 内网速度提升: ${speedup}x`)
|
||
}
|
||
}
|
||
console.log('='.repeat(60))
|
||
}
|
||
|
||
// 运行测试
|
||
runTests()
|
||
.then(() => {
|
||
console.log('\n✅ 测试完成')
|
||
process.exit(0)
|
||
})
|
||
.catch((error) => {
|
||
console.error('\n❌ 测试失败:', error)
|
||
process.exit(1)
|
||
})
|