gongxiangp2/scripts/test-s3-speed.ts

254 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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)
})