You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

644 lines
18 KiB

6 years ago
6 years ago
6 years ago
10 years ago
6 years ago
6 years ago
6 years ago
  1. package command
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "io"
  7. "math"
  8. "math/rand"
  9. "net"
  10. "os"
  11. "runtime"
  12. "runtime/pprof"
  13. "sort"
  14. "strings"
  15. "sync"
  16. "time"
  17. "google.golang.org/grpc"
  18. "github.com/chrislusf/seaweedfs/weed/glog"
  19. "github.com/chrislusf/seaweedfs/weed/operation"
  20. "github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
  21. "github.com/chrislusf/seaweedfs/weed/security"
  22. "github.com/chrislusf/seaweedfs/weed/util"
  23. "github.com/chrislusf/seaweedfs/weed/wdclient"
  24. )
  25. type BenchmarkOptions struct {
  26. masters *string
  27. concurrency *int
  28. numberOfFiles *int
  29. fileSize *int
  30. idListFile *string
  31. write *bool
  32. deletePercentage *int
  33. read *bool
  34. sequentialRead *bool
  35. collection *string
  36. replication *string
  37. cpuprofile *string
  38. maxCpu *int
  39. grpcDialOption grpc.DialOption
  40. masterClient *wdclient.MasterClient
  41. readByGrpc *bool
  42. readByTcp *bool
  43. }
  44. var (
  45. b BenchmarkOptions
  46. sharedBytes []byte
  47. isSecure bool
  48. )
  49. func init() {
  50. cmdBenchmark.Run = runBenchmark // break init cycle
  51. cmdBenchmark.IsDebug = cmdBenchmark.Flag.Bool("debug", false, "verbose debug information")
  52. b.masters = cmdBenchmark.Flag.String("master", "localhost:9333", "SeaweedFS master location")
  53. b.concurrency = cmdBenchmark.Flag.Int("c", 16, "number of concurrent write or read processes")
  54. b.fileSize = cmdBenchmark.Flag.Int("size", 1024, "simulated file size in bytes, with random(0~63) bytes padding")
  55. b.numberOfFiles = cmdBenchmark.Flag.Int("n", 1024*1024, "number of files to write for each thread")
  56. b.idListFile = cmdBenchmark.Flag.String("list", os.TempDir()+"/benchmark_list.txt", "list of uploaded file ids")
  57. b.write = cmdBenchmark.Flag.Bool("write", true, "enable write")
  58. b.deletePercentage = cmdBenchmark.Flag.Int("deletePercent", 0, "the percent of writes that are deletes")
  59. b.read = cmdBenchmark.Flag.Bool("read", true, "enable read")
  60. b.sequentialRead = cmdBenchmark.Flag.Bool("readSequentially", false, "randomly read by ids from \"-list\" specified file")
  61. b.collection = cmdBenchmark.Flag.String("collection", "benchmark", "write data to this collection")
  62. b.replication = cmdBenchmark.Flag.String("replication", "000", "replication type")
  63. b.cpuprofile = cmdBenchmark.Flag.String("cpuprofile", "", "cpu profile output file")
  64. b.maxCpu = cmdBenchmark.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
  65. b.readByGrpc = cmdBenchmark.Flag.Bool("read.grpc", false, "use grpc API to read")
  66. b.readByTcp = cmdBenchmark.Flag.Bool("read.tcp", false, "use tcp API to read")
  67. sharedBytes = make([]byte, 1024)
  68. }
  69. var cmdBenchmark = &Command{
  70. UsageLine: "benchmark -master=localhost:9333 -c=10 -n=100000",
  71. Short: "benchmark on writing millions of files and read out",
  72. Long: `benchmark on an empty SeaweedFS file system.
  73. Two tests during benchmark:
  74. 1) write lots of small files to the system
  75. 2) read the files out
  76. The file content is mostly zero, but no compression is done.
  77. You can choose to only benchmark read or write.
  78. During write, the list of uploaded file ids is stored in "-list" specified file.
  79. You can also use your own list of file ids to run read test.
  80. Write speed and read speed will be collected.
  81. The numbers are used to get a sense of the system.
  82. Usually your network or the hard drive is the real bottleneck.
  83. Another thing to watch is whether the volumes are evenly distributed
  84. to each volume server. Because the 7 more benchmark volumes are randomly distributed
  85. to servers with free slots, it's highly possible some servers have uneven amount of
  86. benchmark volumes. To remedy this, you can use this to grow the benchmark volumes
  87. before starting the benchmark command:
  88. http://localhost:9333/vol/grow?collection=benchmark&count=5
  89. After benchmarking, you can clean up the written data by deleting the benchmark collection
  90. http://localhost:9333/col/delete?collection=benchmark
  91. `,
  92. }
  93. var (
  94. wait sync.WaitGroup
  95. writeStats *stats
  96. readStats *stats
  97. )
  98. func runBenchmark(cmd *Command, args []string) bool {
  99. util.LoadConfiguration("security", false)
  100. b.grpcDialOption = security.LoadClientTLS(util.GetViper(), "grpc.client")
  101. fmt.Printf("This is SeaweedFS version %s %s %s\n", util.VERSION, runtime.GOOS, runtime.GOARCH)
  102. if *b.maxCpu < 1 {
  103. *b.maxCpu = runtime.NumCPU()
  104. }
  105. runtime.GOMAXPROCS(*b.maxCpu)
  106. if *b.cpuprofile != "" {
  107. f, err := os.Create(*b.cpuprofile)
  108. if err != nil {
  109. glog.Fatal(err)
  110. }
  111. pprof.StartCPUProfile(f)
  112. defer pprof.StopCPUProfile()
  113. }
  114. b.masterClient = wdclient.NewMasterClient(context.Background(), b.grpcDialOption, "client", strings.Split(*b.masters, ","))
  115. go b.masterClient.KeepConnectedToMaster()
  116. b.masterClient.WaitUntilConnected()
  117. if *b.write {
  118. benchWrite()
  119. }
  120. if *b.read {
  121. benchRead()
  122. }
  123. return true
  124. }
  125. func benchWrite() {
  126. fileIdLineChan := make(chan string)
  127. finishChan := make(chan bool)
  128. writeStats = newStats(*b.concurrency)
  129. idChan := make(chan int)
  130. go writeFileIds(*b.idListFile, fileIdLineChan, finishChan)
  131. for i := 0; i < *b.concurrency; i++ {
  132. wait.Add(1)
  133. go writeFiles(idChan, fileIdLineChan, &writeStats.localStats[i])
  134. }
  135. writeStats.start = time.Now()
  136. writeStats.total = *b.numberOfFiles
  137. go writeStats.checkProgress("Writing Benchmark", finishChan)
  138. for i := 0; i < *b.numberOfFiles; i++ {
  139. idChan <- i
  140. }
  141. close(idChan)
  142. wait.Wait()
  143. writeStats.end = time.Now()
  144. wait.Add(2)
  145. finishChan <- true
  146. finishChan <- true
  147. wait.Wait()
  148. close(finishChan)
  149. writeStats.printStats()
  150. }
  151. func benchRead() {
  152. fileIdLineChan := make(chan string)
  153. finishChan := make(chan bool)
  154. readStats = newStats(*b.concurrency)
  155. go readFileIds(*b.idListFile, fileIdLineChan)
  156. readStats.start = time.Now()
  157. readStats.total = *b.numberOfFiles
  158. go readStats.checkProgress("Randomly Reading Benchmark", finishChan)
  159. for i := 0; i < *b.concurrency; i++ {
  160. wait.Add(1)
  161. go readFiles(fileIdLineChan, &readStats.localStats[i])
  162. }
  163. wait.Wait()
  164. wait.Add(1)
  165. finishChan <- true
  166. wait.Wait()
  167. close(finishChan)
  168. readStats.end = time.Now()
  169. readStats.printStats()
  170. }
  171. type delayedFile struct {
  172. enterTime time.Time
  173. fp *operation.FilePart
  174. }
  175. func writeFiles(idChan chan int, fileIdLineChan chan string, s *stat) {
  176. defer wait.Done()
  177. delayedDeleteChan := make(chan *delayedFile, 100)
  178. var waitForDeletions sync.WaitGroup
  179. for i := 0; i < 7; i++ {
  180. waitForDeletions.Add(1)
  181. go func() {
  182. defer waitForDeletions.Done()
  183. for df := range delayedDeleteChan {
  184. if df.enterTime.After(time.Now()) {
  185. time.Sleep(df.enterTime.Sub(time.Now()))
  186. }
  187. var jwtAuthorization security.EncodedJwt
  188. if isSecure {
  189. jwtAuthorization = operation.LookupJwt(b.masterClient.GetMaster(), df.fp.Fid)
  190. }
  191. if e := util.Delete(fmt.Sprintf("http://%s/%s", df.fp.Server, df.fp.Fid), string(jwtAuthorization)); e == nil {
  192. s.completed++
  193. } else {
  194. s.failed++
  195. }
  196. }
  197. }()
  198. }
  199. random := rand.New(rand.NewSource(time.Now().UnixNano()))
  200. for id := range idChan {
  201. start := time.Now()
  202. fileSize := int64(*b.fileSize + random.Intn(64))
  203. fp := &operation.FilePart{
  204. Reader: &FakeReader{id: uint64(id), size: fileSize},
  205. FileSize: fileSize,
  206. MimeType: "image/bench", // prevent gzip benchmark content
  207. }
  208. ar := &operation.VolumeAssignRequest{
  209. Count: 1,
  210. Collection: *b.collection,
  211. Replication: *b.replication,
  212. }
  213. if assignResult, err := operation.Assign(b.masterClient.GetMaster(), b.grpcDialOption, ar); err == nil {
  214. fp.Server, fp.Fid, fp.Collection = assignResult.Url, assignResult.Fid, *b.collection
  215. if !isSecure && assignResult.Auth != "" {
  216. isSecure = true
  217. }
  218. if _, err := fp.Upload(0, b.masterClient.GetMaster(), assignResult.Auth, b.grpcDialOption); err == nil {
  219. if random.Intn(100) < *b.deletePercentage {
  220. s.total++
  221. delayedDeleteChan <- &delayedFile{time.Now().Add(time.Second), fp}
  222. } else {
  223. fileIdLineChan <- fp.Fid
  224. }
  225. s.completed++
  226. s.transferred += fileSize
  227. } else {
  228. s.failed++
  229. fmt.Printf("Failed to write with error:%v\n", err)
  230. }
  231. writeStats.addSample(time.Now().Sub(start))
  232. if *cmdBenchmark.IsDebug {
  233. fmt.Printf("writing %d file %s\n", id, fp.Fid)
  234. }
  235. } else {
  236. s.failed++
  237. println("writing file error:", err.Error())
  238. }
  239. }
  240. close(delayedDeleteChan)
  241. waitForDeletions.Wait()
  242. }
  243. func readFiles(fileIdLineChan chan string, s *stat) {
  244. defer wait.Done()
  245. for fid := range fileIdLineChan {
  246. if len(fid) == 0 {
  247. continue
  248. }
  249. if fid[0] == '#' {
  250. continue
  251. }
  252. if *cmdBenchmark.IsDebug {
  253. fmt.Printf("reading file %s\n", fid)
  254. }
  255. start := time.Now()
  256. var bytesRead int
  257. var err error
  258. if *b.readByGrpc {
  259. volumeServer, err := b.masterClient.LookupVolumeServer(fid)
  260. if err != nil {
  261. s.failed++
  262. println("!!!! ", fid, " location not found!!!!!")
  263. continue
  264. }
  265. bytesRead, err = grpcFileGet(volumeServer, fid, b.grpcDialOption)
  266. } else if *b.readByTcp {
  267. volumeServer, err := b.masterClient.LookupVolumeServer(fid)
  268. if err != nil {
  269. s.failed++
  270. println("!!!! ", fid, " location not found!!!!!")
  271. continue
  272. }
  273. bytesRead, err = tcpFileGet(volumeServer, fid)
  274. } else {
  275. url, err := b.masterClient.LookupFileId(fid)
  276. if err != nil {
  277. s.failed++
  278. println("!!!! ", fid, " location not found!!!!!")
  279. continue
  280. }
  281. var bytes []byte
  282. bytes, err = util.Get(url)
  283. bytesRead = len(bytes)
  284. }
  285. if err == nil {
  286. s.completed++
  287. s.transferred += int64(bytesRead)
  288. readStats.addSample(time.Now().Sub(start))
  289. } else {
  290. s.failed++
  291. fmt.Printf("Failed to read %s error:%v\n", fid, err)
  292. }
  293. }
  294. }
  295. func grpcFileGet(volumeServer, fid string, grpcDialOption grpc.DialOption) (bytesRead int, err error) {
  296. err = operation.WithVolumeServerClient(volumeServer, grpcDialOption, func(ctx context.Context, client volume_server_pb.VolumeServerClient) error {
  297. fileGetClient, err := client.FileGet(ctx, &volume_server_pb.FileGetRequest{FileId: fid})
  298. if err != nil {
  299. return err
  300. }
  301. for {
  302. resp, respErr := fileGetClient.Recv()
  303. if resp != nil {
  304. bytesRead += len(resp.Data)
  305. }
  306. if respErr != nil {
  307. if respErr == io.EOF {
  308. return nil
  309. }
  310. return respErr
  311. }
  312. }
  313. })
  314. return
  315. }
  316. func tcpFileGet(volumeServer, fid string) (bytesRead int, err error) {
  317. err = operation.WithVolumeServerTcpConnection(volumeServer, func(conn net.Conn) error {
  318. // println("requesting", fid, "...")
  319. if err := util.WriteMessage(conn, &volume_server_pb.TcpRequestHeader{
  320. Get: &volume_server_pb.FileGetRequest{FileId: fid},
  321. }); err != nil {
  322. return err
  323. }
  324. for {
  325. resp := &volume_server_pb.FileGetResponse{}
  326. // println("reading...")
  327. respErr := util.ReadMessage(conn, resp)
  328. if respErr != nil {
  329. if respErr == io.EOF {
  330. return nil
  331. }
  332. // println("err:", respErr.Error())
  333. return respErr
  334. }
  335. // println("resp size", len(resp.Data))
  336. bytesRead += len(resp.Data)
  337. }
  338. })
  339. return
  340. }
  341. func writeFileIds(fileName string, fileIdLineChan chan string, finishChan chan bool) {
  342. file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
  343. if err != nil {
  344. glog.Fatalf("File to create file %s: %s\n", fileName, err)
  345. }
  346. defer file.Close()
  347. for {
  348. select {
  349. case <-finishChan:
  350. wait.Done()
  351. return
  352. case line := <-fileIdLineChan:
  353. file.Write([]byte(line))
  354. file.Write([]byte("\n"))
  355. }
  356. }
  357. }
  358. func readFileIds(fileName string, fileIdLineChan chan string) {
  359. file, err := os.Open(fileName) // For read access.
  360. if err != nil {
  361. glog.Fatalf("File to read file %s: %s\n", fileName, err)
  362. }
  363. defer file.Close()
  364. random := rand.New(rand.NewSource(time.Now().UnixNano()))
  365. r := bufio.NewReader(file)
  366. if *b.sequentialRead {
  367. for {
  368. if line, err := Readln(r); err == nil {
  369. fileIdLineChan <- string(line)
  370. } else {
  371. break
  372. }
  373. }
  374. } else {
  375. lines := make([]string, 0, readStats.total)
  376. for {
  377. if line, err := Readln(r); err == nil {
  378. lines = append(lines, string(line))
  379. } else {
  380. break
  381. }
  382. }
  383. if len(lines) > 0 {
  384. for i := 0; i < readStats.total; i++ {
  385. fileIdLineChan <- lines[random.Intn(len(lines))]
  386. }
  387. }
  388. }
  389. close(fileIdLineChan)
  390. }
  391. const (
  392. benchResolution = 10000 //0.1 microsecond
  393. benchBucket = 1000000000 / benchResolution
  394. )
  395. // An efficient statics collecting and rendering
  396. type stats struct {
  397. data []int
  398. overflow []int
  399. localStats []stat
  400. start time.Time
  401. end time.Time
  402. total int
  403. }
  404. type stat struct {
  405. completed int
  406. failed int
  407. total int
  408. transferred int64
  409. }
  410. var percentages = []int{50, 66, 75, 80, 90, 95, 98, 99, 100}
  411. func newStats(n int) *stats {
  412. return &stats{
  413. data: make([]int, benchResolution),
  414. overflow: make([]int, 0),
  415. localStats: make([]stat, n),
  416. }
  417. }
  418. func (s *stats) addSample(d time.Duration) {
  419. index := int(d / benchBucket)
  420. if index < 0 {
  421. fmt.Printf("This request takes %3.1f seconds, skipping!\n", float64(index)/10000)
  422. } else if index < len(s.data) {
  423. s.data[int(d/benchBucket)]++
  424. } else {
  425. s.overflow = append(s.overflow, index)
  426. }
  427. }
  428. func (s *stats) checkProgress(testName string, finishChan chan bool) {
  429. fmt.Printf("\n------------ %s ----------\n", testName)
  430. ticker := time.Tick(time.Second)
  431. lastCompleted, lastTransferred, lastTime := 0, int64(0), time.Now()
  432. for {
  433. select {
  434. case <-finishChan:
  435. wait.Done()
  436. return
  437. case t := <-ticker:
  438. completed, transferred, taken, total := 0, int64(0), t.Sub(lastTime), s.total
  439. for _, localStat := range s.localStats {
  440. completed += localStat.completed
  441. transferred += localStat.transferred
  442. total += localStat.total
  443. }
  444. fmt.Printf("Completed %d of %d requests, %3.1f%% %3.1f/s %3.1fMB/s\n",
  445. completed, total, float64(completed)*100/float64(total),
  446. float64(completed-lastCompleted)*float64(int64(time.Second))/float64(int64(taken)),
  447. float64(transferred-lastTransferred)*float64(int64(time.Second))/float64(int64(taken))/float64(1024*1024),
  448. )
  449. lastCompleted, lastTransferred, lastTime = completed, transferred, t
  450. }
  451. }
  452. }
  453. func (s *stats) printStats() {
  454. completed, failed, transferred, total := 0, 0, int64(0), s.total
  455. for _, localStat := range s.localStats {
  456. completed += localStat.completed
  457. failed += localStat.failed
  458. transferred += localStat.transferred
  459. total += localStat.total
  460. }
  461. timeTaken := float64(int64(s.end.Sub(s.start))) / 1000000000
  462. fmt.Printf("\nConcurrency Level: %d\n", *b.concurrency)
  463. fmt.Printf("Time taken for tests: %.3f seconds\n", timeTaken)
  464. fmt.Printf("Complete requests: %d\n", completed)
  465. fmt.Printf("Failed requests: %d\n", failed)
  466. fmt.Printf("Total transferred: %d bytes\n", transferred)
  467. fmt.Printf("Requests per second: %.2f [#/sec]\n", float64(completed)/timeTaken)
  468. fmt.Printf("Transfer rate: %.2f [Kbytes/sec]\n", float64(transferred)/1024/timeTaken)
  469. n, sum := 0, 0
  470. min, max := 10000000, 0
  471. for i := 0; i < len(s.data); i++ {
  472. n += s.data[i]
  473. sum += s.data[i] * i
  474. if s.data[i] > 0 {
  475. if min > i {
  476. min = i
  477. }
  478. if max < i {
  479. max = i
  480. }
  481. }
  482. }
  483. n += len(s.overflow)
  484. for i := 0; i < len(s.overflow); i++ {
  485. sum += s.overflow[i]
  486. if min > s.overflow[i] {
  487. min = s.overflow[i]
  488. }
  489. if max < s.overflow[i] {
  490. max = s.overflow[i]
  491. }
  492. }
  493. avg := float64(sum) / float64(n)
  494. varianceSum := 0.0
  495. for i := 0; i < len(s.data); i++ {
  496. if s.data[i] > 0 {
  497. d := float64(i) - avg
  498. varianceSum += d * d * float64(s.data[i])
  499. }
  500. }
  501. for i := 0; i < len(s.overflow); i++ {
  502. d := float64(s.overflow[i]) - avg
  503. varianceSum += d * d
  504. }
  505. std := math.Sqrt(varianceSum / float64(n))
  506. fmt.Printf("\nConnection Times (ms)\n")
  507. fmt.Printf(" min avg max std\n")
  508. fmt.Printf("Total: %2.1f %3.1f %3.1f %3.1f\n", float32(min)/10, float32(avg)/10, float32(max)/10, std/10)
  509. //printing percentiles
  510. fmt.Printf("\nPercentage of the requests served within a certain time (ms)\n")
  511. percentiles := make([]int, len(percentages))
  512. for i := 0; i < len(percentages); i++ {
  513. percentiles[i] = n * percentages[i] / 100
  514. }
  515. percentiles[len(percentiles)-1] = n
  516. percentileIndex := 0
  517. currentSum := 0
  518. for i := 0; i < len(s.data); i++ {
  519. currentSum += s.data[i]
  520. if s.data[i] > 0 && percentileIndex < len(percentiles) && currentSum >= percentiles[percentileIndex] {
  521. fmt.Printf(" %3d%% %5.1f ms\n", percentages[percentileIndex], float32(i)/10.0)
  522. percentileIndex++
  523. for percentileIndex < len(percentiles) && currentSum >= percentiles[percentileIndex] {
  524. percentileIndex++
  525. }
  526. }
  527. }
  528. sort.Ints(s.overflow)
  529. for i := 0; i < len(s.overflow); i++ {
  530. currentSum++
  531. if percentileIndex < len(percentiles) && currentSum >= percentiles[percentileIndex] {
  532. fmt.Printf(" %3d%% %5.1f ms\n", percentages[percentileIndex], float32(s.overflow[i])/10.0)
  533. percentileIndex++
  534. for percentileIndex < len(percentiles) && currentSum >= percentiles[percentileIndex] {
  535. percentileIndex++
  536. }
  537. }
  538. }
  539. }
  540. // a fake reader to generate content to upload
  541. type FakeReader struct {
  542. id uint64 // an id number
  543. size int64 // max bytes
  544. }
  545. func (l *FakeReader) Read(p []byte) (n int, err error) {
  546. if l.size <= 0 {
  547. return 0, io.EOF
  548. }
  549. if int64(len(p)) > l.size {
  550. n = int(l.size)
  551. } else {
  552. n = len(p)
  553. }
  554. if n >= 8 {
  555. for i := 0; i < 8; i++ {
  556. p[i] = byte(l.id >> uint(i*8))
  557. }
  558. }
  559. l.size -= int64(n)
  560. return
  561. }
  562. func (l *FakeReader) WriteTo(w io.Writer) (n int64, err error) {
  563. size := int(l.size)
  564. bufferSize := len(sharedBytes)
  565. for size > 0 {
  566. tempBuffer := sharedBytes
  567. if size < bufferSize {
  568. tempBuffer = sharedBytes[0:size]
  569. }
  570. count, e := w.Write(tempBuffer)
  571. if e != nil {
  572. return int64(size), e
  573. }
  574. size -= count
  575. }
  576. return l.size, nil
  577. }
  578. func Readln(r *bufio.Reader) ([]byte, error) {
  579. var (
  580. isPrefix = true
  581. err error
  582. line, ln []byte
  583. )
  584. for isPrefix && err == nil {
  585. line, isPrefix, err = r.ReadLine()
  586. ln = append(ln, line...)
  587. }
  588. return ln, err
  589. }