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.
		
		
		
		
		
			
		
			
				
					
					
						
							663 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							663 lines
						
					
					
						
							20 KiB
						
					
					
				| // Package main provides a demonstration server showing SeaweedFS RDMA integration | |
| package main | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"encoding/json" | |
| 	"fmt" | |
| 	"net/http" | |
| 	"os" | |
| 	"os/signal" | |
| 	"strconv" | |
| 	"strings" | |
| 	"syscall" | |
| 	"time" | |
| 
 | |
| 	"seaweedfs-rdma-sidecar/pkg/seaweedfs" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/needle" | |
| 	"github.com/sirupsen/logrus" | |
| 	"github.com/spf13/cobra" | |
| ) | |
| 
 | |
| var ( | |
| 	port            int | |
| 	rdmaSocket      string | |
| 	volumeServerURL string | |
| 	enableRDMA      bool | |
| 	enableZeroCopy  bool | |
| 	tempDir         string | |
| 	enablePooling   bool | |
| 	maxConnections  int | |
| 	maxIdleTime     time.Duration | |
| 	debug           bool | |
| ) | |
| 
 | |
| func main() { | |
| 	var rootCmd = &cobra.Command{ | |
| 		Use:   "demo-server", | |
| 		Short: "SeaweedFS RDMA integration demonstration server", | |
| 		Long: `Demonstration server that shows how SeaweedFS can integrate with the RDMA sidecar | |
| for accelerated read operations. This server provides HTTP endpoints that demonstrate | |
| the RDMA fast path with HTTP fallback capabilities.`, | |
| 		RunE: runServer, | |
| 	} | |
| 
 | |
| 	rootCmd.Flags().IntVarP(&port, "port", "p", 8080, "Demo server HTTP port") | |
| 	rootCmd.Flags().StringVarP(&rdmaSocket, "rdma-socket", "r", "/tmp/rdma-engine.sock", "Path to RDMA engine Unix socket") | |
| 	rootCmd.Flags().StringVarP(&volumeServerURL, "volume-server", "v", "http://localhost:8080", "SeaweedFS volume server URL for HTTP fallback") | |
| 	rootCmd.Flags().BoolVarP(&enableRDMA, "enable-rdma", "e", true, "Enable RDMA acceleration") | |
| 	rootCmd.Flags().BoolVarP(&enableZeroCopy, "enable-zerocopy", "z", true, "Enable zero-copy optimization via temp files") | |
| 	rootCmd.Flags().StringVarP(&tempDir, "temp-dir", "t", "/tmp/rdma-cache", "Temp directory for zero-copy files") | |
| 	rootCmd.Flags().BoolVar(&enablePooling, "enable-pooling", true, "Enable RDMA connection pooling") | |
| 	rootCmd.Flags().IntVar(&maxConnections, "max-connections", 10, "Maximum connections in RDMA pool") | |
| 	rootCmd.Flags().DurationVar(&maxIdleTime, "max-idle-time", 5*time.Minute, "Maximum idle time for pooled connections") | |
| 	rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") | |
| 
 | |
| 	if err := rootCmd.Execute(); err != nil { | |
| 		fmt.Fprintf(os.Stderr, "Error: %v\n", err) | |
| 		os.Exit(1) | |
| 	} | |
| } | |
| 
 | |
| func runServer(cmd *cobra.Command, args []string) error { | |
| 	// Setup logging | |
| 	logger := logrus.New() | |
| 	if debug { | |
| 		logger.SetLevel(logrus.DebugLevel) | |
| 		logger.SetFormatter(&logrus.TextFormatter{ | |
| 			FullTimestamp: true, | |
| 			ForceColors:   true, | |
| 		}) | |
| 	} else { | |
| 		logger.SetLevel(logrus.InfoLevel) | |
| 	} | |
| 
 | |
| 	logger.WithFields(logrus.Fields{ | |
| 		"port":              port, | |
| 		"rdma_socket":       rdmaSocket, | |
| 		"volume_server_url": volumeServerURL, | |
| 		"enable_rdma":       enableRDMA, | |
| 		"enable_zerocopy":   enableZeroCopy, | |
| 		"temp_dir":          tempDir, | |
| 		"enable_pooling":    enablePooling, | |
| 		"max_connections":   maxConnections, | |
| 		"max_idle_time":     maxIdleTime, | |
| 		"debug":             debug, | |
| 	}).Info("🚀 Starting SeaweedFS RDMA Demo Server") | |
| 
 | |
| 	// Create SeaweedFS RDMA client | |
| 	config := &seaweedfs.Config{ | |
| 		RDMASocketPath:  rdmaSocket, | |
| 		VolumeServerURL: volumeServerURL, | |
| 		Enabled:         enableRDMA, | |
| 		DefaultTimeout:  30 * time.Second, | |
| 		Logger:          logger, | |
| 		TempDir:         tempDir, | |
| 		UseZeroCopy:     enableZeroCopy, | |
| 		EnablePooling:   enablePooling, | |
| 		MaxConnections:  maxConnections, | |
| 		MaxIdleTime:     maxIdleTime, | |
| 	} | |
| 
 | |
| 	rdmaClient, err := seaweedfs.NewSeaweedFSRDMAClient(config) | |
| 	if err != nil { | |
| 		return fmt.Errorf("failed to create RDMA client: %w", err) | |
| 	} | |
| 
 | |
| 	// Start RDMA client | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | |
| 	if err := rdmaClient.Start(ctx); err != nil { | |
| 		logger.WithError(err).Error("Failed to start RDMA client") | |
| 	} | |
| 	cancel() | |
| 
 | |
| 	// Create demo server | |
| 	server := &DemoServer{ | |
| 		rdmaClient: rdmaClient, | |
| 		logger:     logger, | |
| 	} | |
| 
 | |
| 	// Setup HTTP routes | |
| 	mux := http.NewServeMux() | |
| 	mux.HandleFunc("/", server.homeHandler) | |
| 	mux.HandleFunc("/health", server.healthHandler) | |
| 	mux.HandleFunc("/stats", server.statsHandler) | |
| 	mux.HandleFunc("/read", server.readHandler) | |
| 	mux.HandleFunc("/benchmark", server.benchmarkHandler) | |
| 	mux.HandleFunc("/cleanup", server.cleanupHandler) | |
| 
 | |
| 	httpServer := &http.Server{ | |
| 		Addr:    fmt.Sprintf(":%d", port), | |
| 		Handler: mux, | |
| 	} | |
| 
 | |
| 	// Handle graceful shutdown | |
| 	sigChan := make(chan os.Signal, 1) | |
| 	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) | |
| 
 | |
| 	go func() { | |
| 		logger.WithField("port", port).Info("🌐 Demo server starting") | |
| 		if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | |
| 			logger.WithError(err).Fatal("HTTP server failed") | |
| 		} | |
| 	}() | |
| 
 | |
| 	// Wait for shutdown signal | |
| 	<-sigChan | |
| 	logger.Info("📡 Received shutdown signal, gracefully shutting down...") | |
| 
 | |
| 	// Shutdown HTTP server | |
| 	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) | |
| 	defer shutdownCancel() | |
| 
 | |
| 	if err := httpServer.Shutdown(shutdownCtx); err != nil { | |
| 		logger.WithError(err).Error("HTTP server shutdown failed") | |
| 	} else { | |
| 		logger.Info("🌐 HTTP server shutdown complete") | |
| 	} | |
| 
 | |
| 	// Stop RDMA client | |
| 	rdmaClient.Stop() | |
| 	logger.Info("🛑 Demo server shutdown complete") | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // DemoServer demonstrates SeaweedFS RDMA integration | |
| type DemoServer struct { | |
| 	rdmaClient *seaweedfs.SeaweedFSRDMAClient | |
| 	logger     *logrus.Logger | |
| } | |
| 
 | |
| // homeHandler provides information about the demo server | |
| func (s *DemoServer) homeHandler(w http.ResponseWriter, r *http.Request) { | |
| 	if r.Method != http.MethodGet { | |
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
| 		return | |
| 	} | |
| 
 | |
| 	w.Header().Set("Content-Type", "text/html") | |
| 	fmt.Fprintf(w, `<!DOCTYPE html> | |
| <html> | |
| <head> | |
|     <title>SeaweedFS RDMA Demo Server</title> | |
|     <style> | |
|         body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; } | |
|         .container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } | |
|         h1 { color: #2c3e50; } | |
|         .endpoint { margin: 20px 0; padding: 15px; background: #ecf0f1; border-radius: 4px; } | |
|         .endpoint h3 { margin: 0 0 10px 0; color: #34495e; } | |
|         .endpoint a { color: #3498db; text-decoration: none; } | |
|         .endpoint a:hover { text-decoration: underline; } | |
|         .status { padding: 10px; border-radius: 4px; margin: 10px 0; } | |
|         .status.enabled { background: #d5f4e6; color: #27ae60; } | |
|         .status.disabled { background: #fadbd8; color: #e74c3c; } | |
|     </style> | |
| </head> | |
| <body> | |
|     <div class="container"> | |
|         <h1>🚀 SeaweedFS RDMA Demo Server</h1> | |
|         <p>This server demonstrates SeaweedFS integration with RDMA acceleration for high-performance reads.</p> | |
|          | |
|         <div class="status %s"> | |
|             <strong>RDMA Status:</strong> %s | |
|         </div> | |
|  | |
|         <h2>📋 Available Endpoints</h2> | |
|          | |
|         <div class="endpoint"> | |
|             <h3>🏥 Health Check</h3> | |
|             <p><a href="/health">/health</a> - Check server and RDMA engine health</p> | |
|         </div> | |
|  | |
|         <div class="endpoint"> | |
|             <h3>📊 Statistics</h3> | |
|             <p><a href="/stats">/stats</a> - Get RDMA client statistics and capabilities</p> | |
|         </div> | |
|  | |
|         <div class="endpoint"> | |
|             <h3>📖 Read Needle</h3> | |
|             <p><a href="/read?file_id=3,01637037d6&size=1024&volume_server=http://localhost:8080">/read</a> - Read a needle with RDMA fast path</p> | |
|             <p><strong>Parameters:</strong> file_id OR (volume, needle, cookie), volume_server, offset (optional), size (optional)</p> | |
|         </div> | |
|  | |
|         <div class="endpoint"> | |
|             <h3>🏁 Benchmark</h3> | |
|             <p><a href="/benchmark?iterations=10&size=4096">/benchmark</a> - Run performance benchmark</p> | |
|             <p><strong>Parameters:</strong> iterations (default: 10), size (default: 4096)</p> | |
|         </div> | |
|  | |
|         <h2>📝 Example Usage</h2> | |
|         <pre> | |
| # Read a needle using file ID (recommended) | |
| curl "http://localhost:%d/read?file_id=3,01637037d6&size=1024&volume_server=http://localhost:8080" | |
|  | |
| # Read a needle using individual parameters (legacy) | |
| curl "http://localhost:%d/read?volume=1&needle=12345&cookie=305419896&size=1024&volume_server=http://localhost:8080" | |
|  | |
| # Read a needle (hex cookie) | |
| curl "http://localhost:%d/read?volume=1&needle=12345&cookie=0x12345678&size=1024&volume_server=http://localhost:8080" | |
|  | |
| # Run benchmark | |
| curl "http://localhost:%d/benchmark?iterations=5&size=2048" | |
|  | |
| # Check health | |
| curl "http://localhost:%d/health" | |
|         </pre> | |
|     </div> | |
| </body> | |
| </html>`, | |
| 		map[bool]string{true: "enabled", false: "disabled"}[s.rdmaClient.IsEnabled()], | |
| 		map[bool]string{true: "RDMA Enabled ✅", false: "RDMA Disabled (HTTP Fallback Only) ⚠️"}[s.rdmaClient.IsEnabled()], | |
| 		port, port, port, port) | |
| } | |
| 
 | |
| // healthHandler checks server and RDMA health | |
| func (s *DemoServer) healthHandler(w http.ResponseWriter, r *http.Request) { | |
| 	if r.Method != http.MethodGet { | |
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
| 		return | |
| 	} | |
| 
 | |
| 	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) | |
| 	defer cancel() | |
| 
 | |
| 	health := map[string]interface{}{ | |
| 		"status":    "healthy", | |
| 		"timestamp": time.Now().Format(time.RFC3339), | |
| 		"rdma": map[string]interface{}{ | |
| 			"enabled":   false, | |
| 			"connected": false, | |
| 		}, | |
| 	} | |
| 
 | |
| 	if s.rdmaClient != nil { | |
| 		health["rdma"].(map[string]interface{})["enabled"] = s.rdmaClient.IsEnabled() | |
| 		health["rdma"].(map[string]interface{})["type"] = "local" | |
| 
 | |
| 		if s.rdmaClient.IsEnabled() { | |
| 			if err := s.rdmaClient.HealthCheck(ctx); err != nil { | |
| 				s.logger.WithError(err).Warn("RDMA health check failed") | |
| 				health["rdma"].(map[string]interface{})["error"] = err.Error() | |
| 			} else { | |
| 				health["rdma"].(map[string]interface{})["connected"] = true | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	w.Header().Set("Content-Type", "application/json") | |
| 	json.NewEncoder(w).Encode(health) | |
| } | |
| 
 | |
| // statsHandler returns RDMA statistics | |
| func (s *DemoServer) statsHandler(w http.ResponseWriter, r *http.Request) { | |
| 	if r.Method != http.MethodGet { | |
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
| 		return | |
| 	} | |
| 
 | |
| 	var stats map[string]interface{} | |
| 
 | |
| 	if s.rdmaClient != nil { | |
| 		stats = s.rdmaClient.GetStats() | |
| 		stats["client_type"] = "local" | |
| 	} else { | |
| 		stats = map[string]interface{}{ | |
| 			"client_type": "none", | |
| 			"error":       "no RDMA client available", | |
| 		} | |
| 	} | |
| 
 | |
| 	stats["timestamp"] = time.Now().Format(time.RFC3339) | |
| 
 | |
| 	w.Header().Set("Content-Type", "application/json") | |
| 	json.NewEncoder(w).Encode(stats) | |
| } | |
| 
 | |
| // readHandler demonstrates needle reading with RDMA | |
| func (s *DemoServer) readHandler(w http.ResponseWriter, r *http.Request) { | |
| 	if r.Method != http.MethodGet { | |
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Parse parameters - support both file_id and individual parameters for backward compatibility | |
| 	query := r.URL.Query() | |
| 	volumeServer := query.Get("volume_server") | |
| 	fileID := query.Get("file_id") | |
| 
 | |
| 	var volumeID, cookie uint64 | |
| 	var needleID uint64 | |
| 	var err error | |
| 
 | |
| 	if fileID != "" { | |
| 		// Use file ID format (e.g., "3,01637037d6") | |
| 		// Extract individual components using existing SeaweedFS parsing | |
| 		fid, parseErr := needle.ParseFileIdFromString(fileID) | |
| 		if parseErr != nil { | |
| 			http.Error(w, fmt.Sprintf("invalid 'file_id' parameter: %v", parseErr), http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 		volumeID = uint64(fid.VolumeId) | |
| 		needleID = uint64(fid.Key) | |
| 		cookie = uint64(fid.Cookie) | |
| 	} else { | |
| 		// Use individual parameters (backward compatibility) | |
| 		volumeID, err = strconv.ParseUint(query.Get("volume"), 10, 32) | |
| 		if err != nil { | |
| 			http.Error(w, "invalid 'volume' parameter", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 
 | |
| 		needleID, err = strconv.ParseUint(query.Get("needle"), 10, 64) | |
| 		if err != nil { | |
| 			http.Error(w, "invalid 'needle' parameter", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 
 | |
| 		// Parse cookie parameter - support both decimal and hexadecimal formats | |
| 		cookieStr := query.Get("cookie") | |
| 		if strings.HasPrefix(strings.ToLower(cookieStr), "0x") { | |
| 			// Parse as hexadecimal (remove "0x" prefix) | |
| 			cookie, err = strconv.ParseUint(cookieStr[2:], 16, 32) | |
| 		} else { | |
| 			// Parse as decimal (default) | |
| 			cookie, err = strconv.ParseUint(cookieStr, 10, 32) | |
| 		} | |
| 		if err != nil { | |
| 			http.Error(w, "invalid 'cookie' parameter (expected decimal or hex with 0x prefix)", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 	} | |
| 
 | |
| 	var offset uint64 | |
| 	if offsetStr := query.Get("offset"); offsetStr != "" { | |
| 		var parseErr error | |
| 		offset, parseErr = strconv.ParseUint(offsetStr, 10, 64) | |
| 		if parseErr != nil { | |
| 			http.Error(w, "invalid 'offset' parameter", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 	} | |
| 
 | |
| 	var size uint64 | |
| 	if sizeStr := query.Get("size"); sizeStr != "" { | |
| 		var parseErr error | |
| 		size, parseErr = strconv.ParseUint(sizeStr, 10, 64) | |
| 		if parseErr != nil { | |
| 			http.Error(w, "invalid 'size' parameter", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 	} | |
| 
 | |
| 	if volumeServer == "" { | |
| 		http.Error(w, "volume_server parameter is required", http.StatusBadRequest) | |
| 		return | |
| 	} | |
| 
 | |
| 	if volumeID == 0 || needleID == 0 { | |
| 		http.Error(w, "volume and needle parameters are required", http.StatusBadRequest) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Note: cookie and size can have defaults for demo purposes when user provides empty values, | |
| 	// but invalid parsing is caught above with proper error responses | |
| 	if cookie == 0 { | |
| 		cookie = 0x12345678 // Default cookie for demo | |
| 	} | |
| 
 | |
| 	if size == 0 { | |
| 		size = 4096 // Default size | |
| 	} | |
| 
 | |
| 	logFields := logrus.Fields{ | |
| 		"volume_server": volumeServer, | |
| 		"volume_id":     volumeID, | |
| 		"needle_id":     needleID, | |
| 		"cookie":        fmt.Sprintf("0x%x", cookie), | |
| 		"offset":        offset, | |
| 		"size":          size, | |
| 	} | |
| 	if fileID != "" { | |
| 		logFields["file_id"] = fileID | |
| 	} | |
| 	s.logger.WithFields(logFields).Info("📖 Processing needle read request") | |
| 
 | |
| 	ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) | |
| 	defer cancel() | |
| 
 | |
| 	start := time.Now() | |
| 	req := &seaweedfs.NeedleReadRequest{ | |
| 		VolumeID:     uint32(volumeID), | |
| 		NeedleID:     needleID, | |
| 		Cookie:       uint32(cookie), | |
| 		Offset:       offset, | |
| 		Size:         size, | |
| 		VolumeServer: volumeServer, | |
| 	} | |
| 
 | |
| 	resp, err := s.rdmaClient.ReadNeedle(ctx, req) | |
| 
 | |
| 	if err != nil { | |
| 		s.logger.WithError(err).Error("❌ Needle read failed") | |
| 		http.Error(w, fmt.Sprintf("Read failed: %v", err), http.StatusInternalServerError) | |
| 		return | |
| 	} | |
| 
 | |
| 	duration := time.Since(start) | |
| 
 | |
| 	s.logger.WithFields(logrus.Fields{ | |
| 		"volume_id": volumeID, | |
| 		"needle_id": needleID, | |
| 		"is_rdma":   resp.IsRDMA, | |
| 		"source":    resp.Source, | |
| 		"duration":  duration, | |
| 		"data_size": len(resp.Data), | |
| 	}).Info("✅ Needle read completed") | |
| 
 | |
| 	// Return metadata and first few bytes | |
| 	result := map[string]interface{}{ | |
| 		"success":       true, | |
| 		"volume_id":     volumeID, | |
| 		"needle_id":     needleID, | |
| 		"cookie":        fmt.Sprintf("0x%x", cookie), | |
| 		"is_rdma":       resp.IsRDMA, | |
| 		"source":        resp.Source, | |
| 		"session_id":    resp.SessionID, | |
| 		"duration":      duration.String(), | |
| 		"data_size":     len(resp.Data), | |
| 		"timestamp":     time.Now().Format(time.RFC3339), | |
| 		"use_temp_file": resp.UseTempFile, | |
| 		"temp_file":     resp.TempFilePath, | |
| 	} | |
| 
 | |
| 	// Set headers for zero-copy optimization | |
| 	if resp.UseTempFile && resp.TempFilePath != "" { | |
| 		w.Header().Set("X-Use-Temp-File", "true") | |
| 		w.Header().Set("X-Temp-File", resp.TempFilePath) | |
| 		w.Header().Set("X-Source", resp.Source) | |
| 		w.Header().Set("X-RDMA-Used", fmt.Sprintf("%t", resp.IsRDMA)) | |
| 
 | |
| 		// For zero-copy, return minimal JSON response and let client read from temp file | |
| 		w.Header().Set("Content-Type", "application/json") | |
| 		json.NewEncoder(w).Encode(result) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Regular response with data | |
| 	w.Header().Set("X-Source", resp.Source) | |
| 	w.Header().Set("X-RDMA-Used", fmt.Sprintf("%t", resp.IsRDMA)) | |
| 
 | |
| 	// Include first 32 bytes as hex for verification | |
| 	if len(resp.Data) > 0 { | |
| 		displayLen := 32 | |
| 		if len(resp.Data) < displayLen { | |
| 			displayLen = len(resp.Data) | |
| 		} | |
| 		result["data_preview"] = fmt.Sprintf("%x", resp.Data[:displayLen]) | |
| 	} | |
| 
 | |
| 	w.Header().Set("Content-Type", "application/json") | |
| 	json.NewEncoder(w).Encode(result) | |
| } | |
| 
 | |
| // benchmarkHandler runs performance benchmarks | |
| func (s *DemoServer) benchmarkHandler(w http.ResponseWriter, r *http.Request) { | |
| 	if r.Method != http.MethodGet { | |
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Parse parameters | |
| 	query := r.URL.Query() | |
| 
 | |
| 	iterations := 10 // default value | |
| 	if iterationsStr := query.Get("iterations"); iterationsStr != "" { | |
| 		var parseErr error | |
| 		iterations, parseErr = strconv.Atoi(iterationsStr) | |
| 		if parseErr != nil { | |
| 			http.Error(w, "invalid 'iterations' parameter", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 	} | |
| 
 | |
| 	size := uint64(4096) // default value | |
| 	if sizeStr := query.Get("size"); sizeStr != "" { | |
| 		var parseErr error | |
| 		size, parseErr = strconv.ParseUint(sizeStr, 10, 64) | |
| 		if parseErr != nil { | |
| 			http.Error(w, "invalid 'size' parameter", http.StatusBadRequest) | |
| 			return | |
| 		} | |
| 	} | |
| 
 | |
| 	if iterations <= 0 { | |
| 		iterations = 10 | |
| 	} | |
| 	if size == 0 { | |
| 		size = 4096 | |
| 	} | |
| 
 | |
| 	s.logger.WithFields(logrus.Fields{ | |
| 		"iterations": iterations, | |
| 		"size":       size, | |
| 	}).Info("🏁 Starting benchmark") | |
| 
 | |
| 	ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) | |
| 	defer cancel() | |
| 
 | |
| 	var rdmaSuccessful, rdmaFailed, httpSuccessful, httpFailed int | |
| 	var totalDuration time.Duration | |
| 	var totalBytes uint64 | |
| 
 | |
| 	startTime := time.Now() | |
| 
 | |
| 	for i := 0; i < iterations; i++ { | |
| 		req := &seaweedfs.NeedleReadRequest{ | |
| 			VolumeID: 1, | |
| 			NeedleID: uint64(i + 1), | |
| 			Cookie:   0x12345678, | |
| 			Offset:   0, | |
| 			Size:     size, | |
| 		} | |
| 
 | |
| 		opStart := time.Now() | |
| 		resp, err := s.rdmaClient.ReadNeedle(ctx, req) | |
| 		opDuration := time.Since(opStart) | |
| 
 | |
| 		if err != nil { | |
| 			httpFailed++ | |
| 			continue | |
| 		} | |
| 
 | |
| 		totalDuration += opDuration | |
| 		totalBytes += uint64(len(resp.Data)) | |
| 
 | |
| 		if resp.IsRDMA { | |
| 			rdmaSuccessful++ | |
| 		} else { | |
| 			httpSuccessful++ | |
| 		} | |
| 	} | |
| 
 | |
| 	benchDuration := time.Since(startTime) | |
| 
 | |
| 	// Calculate statistics | |
| 	totalOperations := rdmaSuccessful + httpSuccessful | |
| 	avgLatency := time.Duration(0) | |
| 	if totalOperations > 0 { | |
| 		avgLatency = totalDuration / time.Duration(totalOperations) | |
| 	} | |
| 
 | |
| 	throughputMBps := float64(totalBytes) / benchDuration.Seconds() / (1024 * 1024) | |
| 	opsPerSec := float64(totalOperations) / benchDuration.Seconds() | |
| 
 | |
| 	result := map[string]interface{}{ | |
| 		"benchmark_results": map[string]interface{}{ | |
| 			"iterations":      iterations, | |
| 			"size_per_op":     size, | |
| 			"total_duration":  benchDuration.String(), | |
| 			"successful_ops":  totalOperations, | |
| 			"failed_ops":      rdmaFailed + httpFailed, | |
| 			"rdma_ops":        rdmaSuccessful, | |
| 			"http_ops":        httpSuccessful, | |
| 			"avg_latency":     avgLatency.String(), | |
| 			"throughput_mbps": fmt.Sprintf("%.2f", throughputMBps), | |
| 			"ops_per_sec":     fmt.Sprintf("%.1f", opsPerSec), | |
| 			"total_bytes":     totalBytes, | |
| 		}, | |
| 		"rdma_enabled": s.rdmaClient.IsEnabled(), | |
| 		"timestamp":    time.Now().Format(time.RFC3339), | |
| 	} | |
| 
 | |
| 	s.logger.WithFields(logrus.Fields{ | |
| 		"iterations":      iterations, | |
| 		"successful_ops":  totalOperations, | |
| 		"rdma_ops":        rdmaSuccessful, | |
| 		"http_ops":        httpSuccessful, | |
| 		"avg_latency":     avgLatency, | |
| 		"throughput_mbps": throughputMBps, | |
| 		"ops_per_sec":     opsPerSec, | |
| 	}).Info("📊 Benchmark completed") | |
| 
 | |
| 	w.Header().Set("Content-Type", "application/json") | |
| 	json.NewEncoder(w).Encode(result) | |
| } | |
| 
 | |
| // cleanupHandler handles temp file cleanup requests from mount clients | |
| func (s *DemoServer) cleanupHandler(w http.ResponseWriter, r *http.Request) { | |
| 	if r.Method != http.MethodDelete { | |
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Get temp file path from query parameters | |
| 	tempFilePath := r.URL.Query().Get("temp_file") | |
| 	if tempFilePath == "" { | |
| 		http.Error(w, "missing 'temp_file' parameter", http.StatusBadRequest) | |
| 		return | |
| 	} | |
| 
 | |
| 	s.logger.WithField("temp_file", tempFilePath).Debug("🗑️ Processing cleanup request") | |
| 
 | |
| 	// Use the RDMA client's cleanup method (which delegates to seaweedfs client) | |
| 	err := s.rdmaClient.CleanupTempFile(tempFilePath) | |
| 	if err != nil { | |
| 		s.logger.WithError(err).WithField("temp_file", tempFilePath).Warn("Failed to cleanup temp file") | |
| 		http.Error(w, fmt.Sprintf("cleanup failed: %v", err), http.StatusInternalServerError) | |
| 		return | |
| 	} | |
| 
 | |
| 	s.logger.WithField("temp_file", tempFilePath).Debug("🧹 Temp file cleanup successful") | |
| 
 | |
| 	// Return success response | |
| 	w.Header().Set("Content-Type", "application/json") | |
| 	response := map[string]interface{}{ | |
| 		"success":   true, | |
| 		"message":   "temp file cleaned up successfully", | |
| 		"temp_file": tempFilePath, | |
| 		"timestamp": time.Now().Format(time.RFC3339), | |
| 	} | |
| 	json.NewEncoder(w).Encode(response) | |
| }
 |