perf_opt.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. package performance
  2. import (
  3. "os"
  4. "sort"
  5. ngxConfig "github.com/0xJacky/Nginx-UI/internal/config"
  6. "github.com/0xJacky/Nginx-UI/internal/nginx"
  7. "github.com/pkg/errors"
  8. "github.com/tufanbarisyildirim/gonginx/config"
  9. "github.com/tufanbarisyildirim/gonginx/dumper"
  10. "github.com/tufanbarisyildirim/gonginx/parser"
  11. "github.com/uozi-tech/cosy/logger"
  12. )
  13. type ProxyCacheConfig struct {
  14. Enabled bool `json:"enabled"`
  15. Path string `json:"path"` // Cache file path
  16. Levels string `json:"levels"` // Cache directory levels
  17. UseTempPath string `json:"use_temp_path"` // Use temporary path (on/off)
  18. KeysZone string `json:"keys_zone"` // Shared memory zone name and size
  19. Inactive string `json:"inactive"` // Time after which inactive cache is removed
  20. MaxSize string `json:"max_size"` // Maximum size of cache
  21. MinFree string `json:"min_free"` // Minimum free space
  22. ManagerFiles string `json:"manager_files"` // Number of files processed by manager
  23. ManagerSleep string `json:"manager_sleep"` // Manager check interval
  24. ManagerThreshold string `json:"manager_threshold"` // Manager processing threshold
  25. LoaderFiles string `json:"loader_files"` // Number of files loaded at once
  26. LoaderSleep string `json:"loader_sleep"` // Loader check interval
  27. LoaderThreshold string `json:"loader_threshold"` // Loader processing threshold
  28. // Additionally, the following parameters are available as part of nginx commercial subscription:
  29. // Purger string `json:"purger"` // Enable cache purger (on/off)
  30. // PurgerFiles string `json:"purger_files"` // Number of files processed by purger
  31. // PurgerSleep string `json:"purger_sleep"` // Purger check interval
  32. // PurgerThreshold string `json:"purger_threshold"` // Purger processing threshold
  33. }
  34. // PerfOpt represents Nginx performance optimization settings
  35. type PerfOpt struct {
  36. WorkerProcesses string `json:"worker_processes"` // auto or number
  37. WorkerConnections string `json:"worker_connections"` // max connections
  38. KeepaliveTimeout string `json:"keepalive_timeout"` // timeout in seconds
  39. Gzip string `json:"gzip"` // on or off
  40. GzipMinLength string `json:"gzip_min_length"` // min length to compress
  41. GzipCompLevel string `json:"gzip_comp_level"` // compression level
  42. ClientMaxBodySize string `json:"client_max_body_size"` // max body size (with unit: k, m, g)
  43. ServerNamesHashBucketSize string `json:"server_names_hash_bucket_size"` // hash bucket size
  44. ClientHeaderBufferSize string `json:"client_header_buffer_size"` // header buffer size (with unit: k, m, g)
  45. ClientBodyBufferSize string `json:"client_body_buffer_size"` // body buffer size (with unit: k, m, g)
  46. ProxyCache ProxyCacheConfig `json:"proxy_cache,omitzero"` // proxy cache settings
  47. }
  48. // UpdatePerfOpt updates the Nginx performance optimization settings
  49. func UpdatePerfOpt(opt *PerfOpt) error {
  50. confPath := nginx.GetConfPath("nginx.conf")
  51. if confPath == "" {
  52. return errors.New("failed to get nginx.conf path")
  53. }
  54. // Read the current configuration
  55. content, err := os.ReadFile(confPath)
  56. if err != nil {
  57. return errors.Wrap(err, "failed to read nginx.conf")
  58. }
  59. // Parse the configuration
  60. p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
  61. conf, err := p.Parse()
  62. if err != nil {
  63. return errors.Wrap(err, "failed to parse nginx.conf")
  64. }
  65. // Process the configuration and update performance settings
  66. updateNginxConfig(conf.Block, opt)
  67. // Dump the updated configuration
  68. updatedConf := dumper.DumpBlock(conf.Block, dumper.IndentedStyle)
  69. return ngxConfig.Save(confPath, updatedConf, nil)
  70. }
  71. // updateNginxConfig updates the performance settings in the Nginx configuration
  72. func updateNginxConfig(block config.IBlock, opt *PerfOpt) {
  73. if block == nil {
  74. return
  75. }
  76. directives := block.GetDirectives()
  77. // Update main context directives
  78. updateOrAddDirective(block, directives, "worker_processes", opt.WorkerProcesses)
  79. // Look for events, http, and other blocks
  80. for _, directive := range directives {
  81. if directive.GetName() == "events" && directive.GetBlock() != nil {
  82. // Update events block directives
  83. eventsBlock := directive.GetBlock()
  84. eventsDirectives := eventsBlock.GetDirectives()
  85. updateOrAddDirective(eventsBlock, eventsDirectives, "worker_connections", opt.WorkerConnections)
  86. } else if directive.GetName() == "http" && directive.GetBlock() != nil {
  87. // Update http block directives
  88. httpBlock := directive.GetBlock()
  89. httpDirectives := httpBlock.GetDirectives()
  90. updateOrAddDirective(httpBlock, httpDirectives, "keepalive_timeout", opt.KeepaliveTimeout)
  91. updateOrAddDirective(httpBlock, httpDirectives, "gzip", opt.Gzip)
  92. updateOrAddDirective(httpBlock, httpDirectives, "gzip_min_length", opt.GzipMinLength)
  93. updateOrAddDirective(httpBlock, httpDirectives, "gzip_comp_level", opt.GzipCompLevel)
  94. updateOrAddDirective(httpBlock, httpDirectives, "client_max_body_size", opt.ClientMaxBodySize)
  95. updateOrAddDirective(httpBlock, httpDirectives, "server_names_hash_bucket_size", opt.ServerNamesHashBucketSize)
  96. updateOrAddDirective(httpBlock, httpDirectives, "client_header_buffer_size", opt.ClientHeaderBufferSize)
  97. updateOrAddDirective(httpBlock, httpDirectives, "client_body_buffer_size", opt.ClientBodyBufferSize)
  98. // Handle proxy_cache_path directive
  99. updateOrRemoveProxyCachePath(httpBlock, httpDirectives, &opt.ProxyCache)
  100. sortDirectives(httpDirectives)
  101. }
  102. }
  103. }
  104. // updateOrAddDirective updates a directive if it exists, or adds it to the block if it doesn't
  105. func updateOrAddDirective(block config.IBlock, directives []config.IDirective, name string, value string) {
  106. if value == "" {
  107. return
  108. }
  109. // Search for existing directive
  110. for _, directive := range directives {
  111. if directive.GetName() == name {
  112. // Update existing directive
  113. if len(directive.GetParameters()) > 0 {
  114. directive.GetParameters()[0].Value = value
  115. }
  116. return
  117. }
  118. }
  119. // If we get here, we need to add a new directive
  120. // Create a new directive and add it to the block
  121. // This requires knowledge of the underlying implementation
  122. // For now, we'll use the Directive type from gonginx/config
  123. newDirective := &config.Directive{
  124. Name: name,
  125. Parameters: []config.Parameter{{Value: value}},
  126. }
  127. // Add the new directive to the block
  128. // This is specific to the gonginx library implementation
  129. switch block := block.(type) {
  130. case *config.Config:
  131. block.Block.Directives = append(block.Block.Directives, newDirective)
  132. case *config.Block:
  133. block.Directives = append(block.Directives, newDirective)
  134. case *config.HTTP:
  135. block.Directives = append(block.Directives, newDirective)
  136. }
  137. }
  138. // sortDirectives sorts directives alphabetically by name
  139. func sortDirectives(directives []config.IDirective) {
  140. sort.SliceStable(directives, func(i, j int) bool {
  141. // Ensure both i and j can return valid names
  142. return directives[i].GetName() < directives[j].GetName()
  143. })
  144. }
  145. // updateOrRemoveProxyCachePath adds or removes the proxy_cache_path directive based on whether it's enabled
  146. func updateOrRemoveProxyCachePath(block config.IBlock, directives []config.IDirective, proxyCache *ProxyCacheConfig) {
  147. // If not enabled, remove the directive if it exists
  148. if !proxyCache.Enabled {
  149. for i, directive := range directives {
  150. if directive.GetName() == "proxy_cache_path" {
  151. // Remove the directive
  152. switch block := block.(type) {
  153. case *config.Block:
  154. block.Directives = append(block.Directives[:i], block.Directives[i+1:]...)
  155. case *config.HTTP:
  156. block.Directives = append(block.Directives[:i], block.Directives[i+1:]...)
  157. }
  158. return
  159. }
  160. }
  161. return
  162. }
  163. // If enabled, build the proxy_cache_path directive with all parameters
  164. params := []config.Parameter{}
  165. // First parameter is the path (required)
  166. if proxyCache.Path != "" {
  167. params = append(params, config.Parameter{Value: proxyCache.Path})
  168. err := os.MkdirAll(proxyCache.Path, 0755)
  169. if err != nil {
  170. logger.Error("failed to create proxy cache path", err)
  171. }
  172. } else {
  173. // No path specified, can't add the directive
  174. return
  175. }
  176. // Add optional parameters
  177. if proxyCache.Levels != "" {
  178. params = append(params, config.Parameter{Value: "levels=" + proxyCache.Levels})
  179. }
  180. if proxyCache.UseTempPath != "" {
  181. params = append(params, config.Parameter{Value: "use_temp_path=" + proxyCache.UseTempPath})
  182. }
  183. if proxyCache.KeysZone != "" {
  184. params = append(params, config.Parameter{Value: "keys_zone=" + proxyCache.KeysZone})
  185. } else {
  186. // keys_zone is required, can't add the directive without it
  187. return
  188. }
  189. if proxyCache.Inactive != "" {
  190. params = append(params, config.Parameter{Value: "inactive=" + proxyCache.Inactive})
  191. }
  192. if proxyCache.MaxSize != "" {
  193. params = append(params, config.Parameter{Value: "max_size=" + proxyCache.MaxSize})
  194. }
  195. if proxyCache.MinFree != "" {
  196. params = append(params, config.Parameter{Value: "min_free=" + proxyCache.MinFree})
  197. }
  198. if proxyCache.ManagerFiles != "" {
  199. params = append(params, config.Parameter{Value: "manager_files=" + proxyCache.ManagerFiles})
  200. }
  201. if proxyCache.ManagerSleep != "" {
  202. params = append(params, config.Parameter{Value: "manager_sleep=" + proxyCache.ManagerSleep})
  203. }
  204. if proxyCache.ManagerThreshold != "" {
  205. params = append(params, config.Parameter{Value: "manager_threshold=" + proxyCache.ManagerThreshold})
  206. }
  207. if proxyCache.LoaderFiles != "" {
  208. params = append(params, config.Parameter{Value: "loader_files=" + proxyCache.LoaderFiles})
  209. }
  210. if proxyCache.LoaderSleep != "" {
  211. params = append(params, config.Parameter{Value: "loader_sleep=" + proxyCache.LoaderSleep})
  212. }
  213. if proxyCache.LoaderThreshold != "" {
  214. params = append(params, config.Parameter{Value: "loader_threshold=" + proxyCache.LoaderThreshold})
  215. }
  216. // if proxyCache.Purger != "" {
  217. // params = append(params, config.Parameter{Value: "purger=" + proxyCache.Purger})
  218. // }
  219. // if proxyCache.PurgerFiles != "" {
  220. // params = append(params, config.Parameter{Value: "purger_files=" + proxyCache.PurgerFiles})
  221. // }
  222. // if proxyCache.PurgerSleep != "" {
  223. // params = append(params, config.Parameter{Value: "purger_sleep=" + proxyCache.PurgerSleep})
  224. // }
  225. // if proxyCache.PurgerThreshold != "" {
  226. // params = append(params, config.Parameter{Value: "purger_threshold=" + proxyCache.PurgerThreshold})
  227. // }
  228. // Check if directive already exists
  229. for i, directive := range directives {
  230. if directive.GetName() == "proxy_cache_path" {
  231. // Remove the old directive
  232. switch block := block.(type) {
  233. case *config.HTTP:
  234. block.Directives = append(block.Directives[:i], block.Directives[i+1:]...)
  235. }
  236. break
  237. }
  238. }
  239. // Create new directive
  240. newDirective := &config.Directive{
  241. Name: "proxy_cache_path",
  242. Parameters: params,
  243. }
  244. // Add the directive to the block
  245. switch block := block.(type) {
  246. case *config.HTTP:
  247. block.Directives = append(block.Directives, newDirective)
  248. }
  249. }