channel-test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. package controller
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "math"
  9. "net/http"
  10. "net/http/httptest"
  11. "net/url"
  12. "one-api/common"
  13. "one-api/constant"
  14. "one-api/dto"
  15. "one-api/middleware"
  16. "one-api/model"
  17. "one-api/relay"
  18. relaycommon "one-api/relay/common"
  19. relayconstant "one-api/relay/constant"
  20. "one-api/relay/helper"
  21. "one-api/service"
  22. "one-api/setting/operation_setting"
  23. "one-api/types"
  24. "strconv"
  25. "strings"
  26. "sync"
  27. "time"
  28. "github.com/bytedance/gopkg/util/gopool"
  29. "github.com/samber/lo"
  30. "github.com/gin-gonic/gin"
  31. )
  32. type testResult struct {
  33. context *gin.Context
  34. localErr error
  35. newAPIError *types.NewAPIError
  36. }
  37. func testChannel(channel *model.Channel, testModel string, endpointType string) testResult {
  38. tik := time.Now()
  39. var unsupportedTestChannelTypes = []int{
  40. constant.ChannelTypeMidjourney,
  41. constant.ChannelTypeMidjourneyPlus,
  42. constant.ChannelTypeSunoAPI,
  43. constant.ChannelTypeKling,
  44. constant.ChannelTypeJimeng,
  45. constant.ChannelTypeDoubaoVideo,
  46. constant.ChannelTypeVidu,
  47. }
  48. if lo.Contains(unsupportedTestChannelTypes, channel.Type) {
  49. channelTypeName := constant.GetChannelTypeName(channel.Type)
  50. return testResult{
  51. localErr: fmt.Errorf("%s channel test is not supported", channelTypeName),
  52. }
  53. }
  54. w := httptest.NewRecorder()
  55. c, _ := gin.CreateTestContext(w)
  56. requestPath := "/v1/chat/completions"
  57. // 如果指定了端点类型,使用指定的端点类型
  58. if endpointType != "" {
  59. if endpointInfo, ok := common.GetDefaultEndpointInfo(constant.EndpointType(endpointType)); ok {
  60. requestPath = endpointInfo.Path
  61. }
  62. } else {
  63. // 如果没有指定端点类型,使用原有的自动检测逻辑
  64. // 先判断是否为 Embedding 模型
  65. if strings.Contains(strings.ToLower(testModel), "embedding") ||
  66. strings.HasPrefix(testModel, "m3e") || // m3e 系列模型
  67. strings.Contains(testModel, "bge-") || // bge 系列模型
  68. strings.Contains(testModel, "embed") ||
  69. channel.Type == constant.ChannelTypeMokaAI { // 其他 embedding 模型
  70. requestPath = "/v1/embeddings" // 修改请求路径
  71. }
  72. // VolcEngine 图像生成模型
  73. if channel.Type == constant.ChannelTypeVolcEngine && strings.Contains(testModel, "seedream") {
  74. requestPath = "/v1/images/generations"
  75. }
  76. }
  77. c.Request = &http.Request{
  78. Method: "POST",
  79. URL: &url.URL{Path: requestPath}, // 使用动态路径
  80. Body: nil,
  81. Header: make(http.Header),
  82. }
  83. if testModel == "" {
  84. if channel.TestModel != nil && *channel.TestModel != "" {
  85. testModel = *channel.TestModel
  86. } else {
  87. if len(channel.GetModels()) > 0 {
  88. testModel = channel.GetModels()[0]
  89. } else {
  90. testModel = "gpt-4o-mini"
  91. }
  92. }
  93. }
  94. cache, err := model.GetUserCache(1)
  95. if err != nil {
  96. return testResult{
  97. localErr: err,
  98. newAPIError: nil,
  99. }
  100. }
  101. cache.WriteContext(c)
  102. //c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
  103. c.Request.Header.Set("Content-Type", "application/json")
  104. c.Set("channel", channel.Type)
  105. c.Set("base_url", channel.GetBaseURL())
  106. group, _ := model.GetUserGroup(1, false)
  107. c.Set("group", group)
  108. newAPIError := middleware.SetupContextForSelectedChannel(c, channel, testModel)
  109. if newAPIError != nil {
  110. return testResult{
  111. context: c,
  112. localErr: newAPIError,
  113. newAPIError: newAPIError,
  114. }
  115. }
  116. // Determine relay format based on endpoint type or request path
  117. var relayFormat types.RelayFormat
  118. if endpointType != "" {
  119. // 根据指定的端点类型设置 relayFormat
  120. switch constant.EndpointType(endpointType) {
  121. case constant.EndpointTypeOpenAI:
  122. relayFormat = types.RelayFormatOpenAI
  123. case constant.EndpointTypeOpenAIResponse:
  124. relayFormat = types.RelayFormatOpenAIResponses
  125. case constant.EndpointTypeAnthropic:
  126. relayFormat = types.RelayFormatClaude
  127. case constant.EndpointTypeGemini:
  128. relayFormat = types.RelayFormatGemini
  129. case constant.EndpointTypeJinaRerank:
  130. relayFormat = types.RelayFormatRerank
  131. case constant.EndpointTypeImageGeneration:
  132. relayFormat = types.RelayFormatOpenAIImage
  133. case constant.EndpointTypeEmbeddings:
  134. relayFormat = types.RelayFormatEmbedding
  135. default:
  136. relayFormat = types.RelayFormatOpenAI
  137. }
  138. } else {
  139. // 根据请求路径自动检测
  140. relayFormat = types.RelayFormatOpenAI
  141. if c.Request.URL.Path == "/v1/embeddings" {
  142. relayFormat = types.RelayFormatEmbedding
  143. }
  144. if c.Request.URL.Path == "/v1/images/generations" {
  145. relayFormat = types.RelayFormatOpenAIImage
  146. }
  147. if c.Request.URL.Path == "/v1/messages" {
  148. relayFormat = types.RelayFormatClaude
  149. }
  150. if strings.Contains(c.Request.URL.Path, "/v1beta/models") {
  151. relayFormat = types.RelayFormatGemini
  152. }
  153. if c.Request.URL.Path == "/v1/rerank" || c.Request.URL.Path == "/rerank" {
  154. relayFormat = types.RelayFormatRerank
  155. }
  156. if c.Request.URL.Path == "/v1/responses" {
  157. relayFormat = types.RelayFormatOpenAIResponses
  158. }
  159. }
  160. request := buildTestRequest(testModel, endpointType)
  161. info, err := relaycommon.GenRelayInfo(c, relayFormat, request, nil)
  162. if err != nil {
  163. return testResult{
  164. context: c,
  165. localErr: err,
  166. newAPIError: types.NewError(err, types.ErrorCodeGenRelayInfoFailed),
  167. }
  168. }
  169. info.InitChannelMeta(c)
  170. err = helper.ModelMappedHelper(c, info, request)
  171. if err != nil {
  172. return testResult{
  173. context: c,
  174. localErr: err,
  175. newAPIError: types.NewError(err, types.ErrorCodeChannelModelMappedError),
  176. }
  177. }
  178. testModel = info.UpstreamModelName
  179. // 更新请求中的模型名称
  180. request.SetModelName(testModel)
  181. apiType, _ := common.ChannelType2APIType(channel.Type)
  182. adaptor := relay.GetAdaptor(apiType)
  183. if adaptor == nil {
  184. return testResult{
  185. context: c,
  186. localErr: fmt.Errorf("invalid api type: %d, adaptor is nil", apiType),
  187. newAPIError: types.NewError(fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), types.ErrorCodeInvalidApiType),
  188. }
  189. }
  190. //// 创建一个用于日志的 info 副本,移除 ApiKey
  191. //logInfo := info
  192. //logInfo.ApiKey = ""
  193. common.SysLog(fmt.Sprintf("testing channel %d with model %s , info %+v ", channel.Id, testModel, info.ToString()))
  194. priceData, err := helper.ModelPriceHelper(c, info, 0, request.GetTokenCountMeta())
  195. if err != nil {
  196. return testResult{
  197. context: c,
  198. localErr: err,
  199. newAPIError: types.NewError(err, types.ErrorCodeModelPriceError),
  200. }
  201. }
  202. adaptor.Init(info)
  203. var convertedRequest any
  204. // 根据 RelayMode 选择正确的转换函数
  205. switch info.RelayMode {
  206. case relayconstant.RelayModeEmbeddings:
  207. // Embedding 请求 - request 已经是正确的类型
  208. if embeddingReq, ok := request.(*dto.EmbeddingRequest); ok {
  209. convertedRequest, err = adaptor.ConvertEmbeddingRequest(c, info, *embeddingReq)
  210. } else {
  211. return testResult{
  212. context: c,
  213. localErr: errors.New("invalid embedding request type"),
  214. newAPIError: types.NewError(errors.New("invalid embedding request type"), types.ErrorCodeConvertRequestFailed),
  215. }
  216. }
  217. case relayconstant.RelayModeImagesGenerations:
  218. // 图像生成请求 - request 已经是正确的类型
  219. if imageReq, ok := request.(*dto.ImageRequest); ok {
  220. convertedRequest, err = adaptor.ConvertImageRequest(c, info, *imageReq)
  221. } else {
  222. return testResult{
  223. context: c,
  224. localErr: errors.New("invalid image request type"),
  225. newAPIError: types.NewError(errors.New("invalid image request type"), types.ErrorCodeConvertRequestFailed),
  226. }
  227. }
  228. case relayconstant.RelayModeRerank:
  229. // Rerank 请求 - request 已经是正确的类型
  230. if rerankReq, ok := request.(*dto.RerankRequest); ok {
  231. convertedRequest, err = adaptor.ConvertRerankRequest(c, info.RelayMode, *rerankReq)
  232. } else {
  233. return testResult{
  234. context: c,
  235. localErr: errors.New("invalid rerank request type"),
  236. newAPIError: types.NewError(errors.New("invalid rerank request type"), types.ErrorCodeConvertRequestFailed),
  237. }
  238. }
  239. case relayconstant.RelayModeResponses:
  240. // Response 请求 - request 已经是正确的类型
  241. if responseReq, ok := request.(*dto.OpenAIResponsesRequest); ok {
  242. convertedRequest, err = adaptor.ConvertOpenAIResponsesRequest(c, info, *responseReq)
  243. } else {
  244. return testResult{
  245. context: c,
  246. localErr: errors.New("invalid response request type"),
  247. newAPIError: types.NewError(errors.New("invalid response request type"), types.ErrorCodeConvertRequestFailed),
  248. }
  249. }
  250. default:
  251. // Chat/Completion 等其他请求类型
  252. if generalReq, ok := request.(*dto.GeneralOpenAIRequest); ok {
  253. convertedRequest, err = adaptor.ConvertOpenAIRequest(c, info, generalReq)
  254. } else {
  255. return testResult{
  256. context: c,
  257. localErr: errors.New("invalid general request type"),
  258. newAPIError: types.NewError(errors.New("invalid general request type"), types.ErrorCodeConvertRequestFailed),
  259. }
  260. }
  261. }
  262. if err != nil {
  263. return testResult{
  264. context: c,
  265. localErr: err,
  266. newAPIError: types.NewError(err, types.ErrorCodeConvertRequestFailed),
  267. }
  268. }
  269. jsonData, err := json.Marshal(convertedRequest)
  270. if err != nil {
  271. return testResult{
  272. context: c,
  273. localErr: err,
  274. newAPIError: types.NewError(err, types.ErrorCodeJsonMarshalFailed),
  275. }
  276. }
  277. requestBody := bytes.NewBuffer(jsonData)
  278. c.Request.Body = io.NopCloser(requestBody)
  279. resp, err := adaptor.DoRequest(c, info, requestBody)
  280. if err != nil {
  281. return testResult{
  282. context: c,
  283. localErr: err,
  284. newAPIError: types.NewOpenAIError(err, types.ErrorCodeDoRequestFailed, http.StatusInternalServerError),
  285. }
  286. }
  287. var httpResp *http.Response
  288. if resp != nil {
  289. httpResp = resp.(*http.Response)
  290. if httpResp.StatusCode != http.StatusOK {
  291. err := service.RelayErrorHandler(c.Request.Context(), httpResp, true)
  292. return testResult{
  293. context: c,
  294. localErr: err,
  295. newAPIError: types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError),
  296. }
  297. }
  298. }
  299. usageA, respErr := adaptor.DoResponse(c, httpResp, info)
  300. if respErr != nil {
  301. return testResult{
  302. context: c,
  303. localErr: respErr,
  304. newAPIError: respErr,
  305. }
  306. }
  307. if usageA == nil {
  308. return testResult{
  309. context: c,
  310. localErr: errors.New("usage is nil"),
  311. newAPIError: types.NewOpenAIError(errors.New("usage is nil"), types.ErrorCodeBadResponseBody, http.StatusInternalServerError),
  312. }
  313. }
  314. usage := usageA.(*dto.Usage)
  315. result := w.Result()
  316. respBody, err := io.ReadAll(result.Body)
  317. if err != nil {
  318. return testResult{
  319. context: c,
  320. localErr: err,
  321. newAPIError: types.NewOpenAIError(err, types.ErrorCodeReadResponseBodyFailed, http.StatusInternalServerError),
  322. }
  323. }
  324. info.PromptTokens = usage.PromptTokens
  325. quota := 0
  326. if !priceData.UsePrice {
  327. quota = usage.PromptTokens + int(math.Round(float64(usage.CompletionTokens)*priceData.CompletionRatio))
  328. quota = int(math.Round(float64(quota) * priceData.ModelRatio))
  329. if priceData.ModelRatio != 0 && quota <= 0 {
  330. quota = 1
  331. }
  332. } else {
  333. quota = int(priceData.ModelPrice * common.QuotaPerUnit)
  334. }
  335. tok := time.Now()
  336. milliseconds := tok.Sub(tik).Milliseconds()
  337. consumedTime := float64(milliseconds) / 1000.0
  338. other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.CompletionRatio,
  339. usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
  340. model.RecordConsumeLog(c, 1, model.RecordConsumeLogParams{
  341. ChannelId: channel.Id,
  342. PromptTokens: usage.PromptTokens,
  343. CompletionTokens: usage.CompletionTokens,
  344. ModelName: info.OriginModelName,
  345. TokenName: "模型测试",
  346. Quota: quota,
  347. Content: "模型测试",
  348. UseTimeSeconds: int(consumedTime),
  349. IsStream: info.IsStream,
  350. Group: info.UsingGroup,
  351. Other: other,
  352. })
  353. common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
  354. return testResult{
  355. context: c,
  356. localErr: nil,
  357. newAPIError: nil,
  358. }
  359. }
  360. func buildTestRequest(model string, endpointType string) dto.Request {
  361. // 根据端点类型构建不同的测试请求
  362. if endpointType != "" {
  363. switch constant.EndpointType(endpointType) {
  364. case constant.EndpointTypeEmbeddings:
  365. // 返回 EmbeddingRequest
  366. return &dto.EmbeddingRequest{
  367. Model: model,
  368. Input: []any{"hello world"},
  369. }
  370. case constant.EndpointTypeImageGeneration:
  371. // 返回 ImageRequest
  372. return &dto.ImageRequest{
  373. Model: model,
  374. Prompt: "a cute cat",
  375. N: 1,
  376. Size: "1024x1024",
  377. }
  378. case constant.EndpointTypeJinaRerank:
  379. // 返回 RerankRequest
  380. return &dto.RerankRequest{
  381. Model: model,
  382. Query: "What is Deep Learning?",
  383. Documents: []any{"Deep Learning is a subset of machine learning.", "Machine learning is a field of artificial intelligence."},
  384. TopN: 2,
  385. }
  386. case constant.EndpointTypeOpenAIResponse:
  387. // 返回 OpenAIResponsesRequest
  388. return &dto.OpenAIResponsesRequest{
  389. Model: model,
  390. Input: json.RawMessage("\"hi\""),
  391. }
  392. case constant.EndpointTypeAnthropic, constant.EndpointTypeGemini, constant.EndpointTypeOpenAI:
  393. // 返回 GeneralOpenAIRequest
  394. maxTokens := uint(10)
  395. if constant.EndpointType(endpointType) == constant.EndpointTypeGemini {
  396. maxTokens = 3000
  397. }
  398. return &dto.GeneralOpenAIRequest{
  399. Model: model,
  400. Stream: false,
  401. Messages: []dto.Message{
  402. {
  403. Role: "user",
  404. Content: "hi",
  405. },
  406. },
  407. MaxTokens: maxTokens,
  408. }
  409. }
  410. }
  411. // 自动检测逻辑(保持原有行为)
  412. // 先判断是否为 Embedding 模型
  413. if strings.Contains(strings.ToLower(model), "embedding") ||
  414. strings.HasPrefix(model, "m3e") ||
  415. strings.Contains(model, "bge-") {
  416. // 返回 EmbeddingRequest
  417. return &dto.EmbeddingRequest{
  418. Model: model,
  419. Input: []any{"hello world"},
  420. }
  421. }
  422. // Chat/Completion 请求 - 返回 GeneralOpenAIRequest
  423. testRequest := &dto.GeneralOpenAIRequest{
  424. Model: model,
  425. Stream: false,
  426. Messages: []dto.Message{
  427. {
  428. Role: "user",
  429. Content: "hi",
  430. },
  431. },
  432. }
  433. if strings.HasPrefix(model, "o") {
  434. testRequest.MaxCompletionTokens = 10
  435. } else if strings.Contains(model, "thinking") {
  436. if !strings.Contains(model, "claude") {
  437. testRequest.MaxTokens = 50
  438. }
  439. } else if strings.Contains(model, "gemini") {
  440. testRequest.MaxTokens = 3000
  441. } else {
  442. testRequest.MaxTokens = 10
  443. }
  444. return testRequest
  445. }
  446. func TestChannel(c *gin.Context) {
  447. channelId, err := strconv.Atoi(c.Param("id"))
  448. if err != nil {
  449. common.ApiError(c, err)
  450. return
  451. }
  452. channel, err := model.CacheGetChannel(channelId)
  453. if err != nil {
  454. channel, err = model.GetChannelById(channelId, true)
  455. if err != nil {
  456. common.ApiError(c, err)
  457. return
  458. }
  459. }
  460. //defer func() {
  461. // if channel.ChannelInfo.IsMultiKey {
  462. // go func() { _ = channel.SaveChannelInfo() }()
  463. // }
  464. //}()
  465. testModel := c.Query("model")
  466. endpointType := c.Query("endpoint_type")
  467. tik := time.Now()
  468. result := testChannel(channel, testModel, endpointType)
  469. if result.localErr != nil {
  470. c.JSON(http.StatusOK, gin.H{
  471. "success": false,
  472. "message": result.localErr.Error(),
  473. "time": 0.0,
  474. })
  475. return
  476. }
  477. tok := time.Now()
  478. milliseconds := tok.Sub(tik).Milliseconds()
  479. go channel.UpdateResponseTime(milliseconds)
  480. consumedTime := float64(milliseconds) / 1000.0
  481. if result.newAPIError != nil {
  482. c.JSON(http.StatusOK, gin.H{
  483. "success": false,
  484. "message": result.newAPIError.Error(),
  485. "time": consumedTime,
  486. })
  487. return
  488. }
  489. c.JSON(http.StatusOK, gin.H{
  490. "success": true,
  491. "message": "",
  492. "time": consumedTime,
  493. })
  494. }
  495. var testAllChannelsLock sync.Mutex
  496. var testAllChannelsRunning bool = false
  497. func testAllChannels(notify bool) error {
  498. testAllChannelsLock.Lock()
  499. if testAllChannelsRunning {
  500. testAllChannelsLock.Unlock()
  501. return errors.New("测试已在运行中")
  502. }
  503. testAllChannelsRunning = true
  504. testAllChannelsLock.Unlock()
  505. channels, getChannelErr := model.GetAllChannels(0, 0, true, false)
  506. if getChannelErr != nil {
  507. return getChannelErr
  508. }
  509. var disableThreshold = int64(common.ChannelDisableThreshold * 1000)
  510. if disableThreshold == 0 {
  511. disableThreshold = 10000000 // a impossible value
  512. }
  513. gopool.Go(func() {
  514. // 使用 defer 确保无论如何都会重置运行状态,防止死锁
  515. defer func() {
  516. testAllChannelsLock.Lock()
  517. testAllChannelsRunning = false
  518. testAllChannelsLock.Unlock()
  519. }()
  520. for _, channel := range channels {
  521. isChannelEnabled := channel.Status == common.ChannelStatusEnabled
  522. tik := time.Now()
  523. result := testChannel(channel, "", "")
  524. tok := time.Now()
  525. milliseconds := tok.Sub(tik).Milliseconds()
  526. shouldBanChannel := false
  527. newAPIError := result.newAPIError
  528. // request error disables the channel
  529. if newAPIError != nil {
  530. shouldBanChannel = service.ShouldDisableChannel(channel.Type, result.newAPIError)
  531. }
  532. // 当错误检查通过,才检查响应时间
  533. if common.AutomaticDisableChannelEnabled && !shouldBanChannel {
  534. if milliseconds > disableThreshold {
  535. err := fmt.Errorf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0)
  536. newAPIError = types.NewOpenAIError(err, types.ErrorCodeChannelResponseTimeExceeded, http.StatusRequestTimeout)
  537. shouldBanChannel = true
  538. }
  539. }
  540. // disable channel
  541. if isChannelEnabled && shouldBanChannel && channel.GetAutoBan() {
  542. processChannelError(result.context, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(result.context, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError)
  543. }
  544. // enable channel
  545. if !isChannelEnabled && service.ShouldEnableChannel(newAPIError, channel.Status) {
  546. service.EnableChannel(channel.Id, common.GetContextKeyString(result.context, constant.ContextKeyChannelKey), channel.Name)
  547. }
  548. channel.UpdateResponseTime(milliseconds)
  549. time.Sleep(common.RequestInterval)
  550. }
  551. if notify {
  552. service.NotifyRootUser(dto.NotifyTypeChannelTest, "通道测试完成", "所有通道测试已完成")
  553. }
  554. })
  555. return nil
  556. }
  557. func TestAllChannels(c *gin.Context) {
  558. err := testAllChannels(true)
  559. if err != nil {
  560. common.ApiError(c, err)
  561. return
  562. }
  563. c.JSON(http.StatusOK, gin.H{
  564. "success": true,
  565. "message": "",
  566. })
  567. }
  568. var autoTestChannelsOnce sync.Once
  569. func AutomaticallyTestChannels() {
  570. autoTestChannelsOnce.Do(func() {
  571. for {
  572. if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled {
  573. time.Sleep(10 * time.Minute)
  574. continue
  575. }
  576. frequency := operation_setting.GetMonitorSetting().AutoTestChannelMinutes
  577. common.SysLog(fmt.Sprintf("automatically test channels with interval %d minutes", frequency))
  578. for {
  579. time.Sleep(time.Duration(frequency) * time.Minute)
  580. common.SysLog("automatically testing all channels")
  581. _ = testAllChannels(false)
  582. common.SysLog("automatically channel test finished")
  583. if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled {
  584. break
  585. }
  586. }
  587. }
  588. })
  589. }