mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2026-01-06 05:09:15 +08:00
feat: Optimize urltest and remove useless features
This commit is contained in:
parent
7b9dff74b1
commit
c5da6e88de
@ -51,15 +51,18 @@ namespace NekoGui {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Common
|
||||
|
||||
std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport) {
|
||||
std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport, int chainID) {
|
||||
auto result = std::make_shared<BuildConfigResult>();
|
||||
auto status = std::make_shared<BuildConfigStatus>();
|
||||
status->ent = ent;
|
||||
status->result = result;
|
||||
status->forTest = forTest;
|
||||
status->forExport = forExport;
|
||||
status->chainID = chainID;
|
||||
|
||||
auto customBean = dynamic_cast<NekoGui_fmt::CustomBean *>(ent->bean.get());
|
||||
if (customBean != nullptr && customBean->core == "internal-full") {
|
||||
@ -74,6 +77,80 @@ namespace NekoGui {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<BuildTestConfigResult> BuildTestConfig(QList<std::shared_ptr<ProxyEntity>> profiles) {
|
||||
auto results = std::make_shared<BuildTestConfigResult>();
|
||||
|
||||
auto idx = 1;
|
||||
QJsonArray outboundArray = {
|
||||
QJsonObject{
|
||||
{"type", "direct"},
|
||||
{"tag", "direct"}
|
||||
},
|
||||
QJsonObject{
|
||||
{"type", "block"},
|
||||
{"tag", "block"}
|
||||
},
|
||||
QJsonObject{
|
||||
{"type", "dns"},
|
||||
{"tag", "dns-out"}
|
||||
}
|
||||
};
|
||||
QJsonArray directDomainArray;
|
||||
for (const auto &item: profiles) {
|
||||
auto res = BuildConfig(item, true, false, idx++);
|
||||
if (!res->error.isEmpty()) {
|
||||
results->error = res->error;
|
||||
return results;
|
||||
}
|
||||
if (item->CustomBean() != nullptr && item->CustomBean()->core == "internal-full") {
|
||||
res->coreConfig["inbounds"] = QJsonArray();
|
||||
results->fullConfigs[item->id] = QJsonObject2QString(res->coreConfig, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// not full config, process it
|
||||
if (results->coreConfig.isEmpty()) {
|
||||
results->coreConfig = res->coreConfig;
|
||||
}
|
||||
// add the direct dns domains
|
||||
for (const auto &rule: res->coreConfig["dns"].toObject()["rules"].toArray()) {
|
||||
if (rule.toObject().contains("domain")) {
|
||||
for (const auto &domain: rule.toObject()["domain"].toArray()) {
|
||||
directDomainArray.append(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
// now we add the outbounds of the current config to the final one
|
||||
auto outbounds = res->coreConfig["outbounds"].toArray();
|
||||
if (outbounds.isEmpty()) {
|
||||
results->error = QString("outbounds is empty for %1").arg(item->bean->name);
|
||||
return results;
|
||||
}
|
||||
auto tag = outbounds[0].toObject()["tag"].toString();
|
||||
results->outboundTags << tag;
|
||||
results->tag2entID.insert(tag, item->id);
|
||||
for (const auto &outboundRef: outbounds) {
|
||||
auto outbound = outboundRef.toObject();
|
||||
if (outbound["tag"] == "direct" || outbound["tag"] == "block" || outbound["tag"] == "dns-out") continue;
|
||||
outboundArray.append(outbound);
|
||||
}
|
||||
}
|
||||
|
||||
results->coreConfig["outbounds"] = outboundArray;
|
||||
auto dnsObj = results->coreConfig["dns"].toObject();
|
||||
auto dnsRulesObj = QJsonArray();
|
||||
if (!directDomainArray.empty()) {
|
||||
dnsRulesObj += QJsonObject{
|
||||
{"domain", directDomainArray},
|
||||
{"server", "dns-direct"}
|
||||
};
|
||||
}
|
||||
dnsObj["rules"] = dnsRulesObj;
|
||||
results->coreConfig["dns"] = dnsObj;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QString BuildChain(int chainId, const std::shared_ptr<BuildConfigStatus> &status) {
|
||||
auto group = profileManager->GetGroup(status->ent->gid);
|
||||
if (group == nullptr) {
|
||||
@ -128,7 +205,7 @@ namespace NekoGui {
|
||||
}
|
||||
|
||||
// BuildChain
|
||||
QString chainTagOut = BuildChainInternal(0, ents, status);
|
||||
QString chainTagOut = BuildChainInternal(chainId, ents, status);
|
||||
|
||||
// Chain ent traffic stat
|
||||
if (ents.length() > 1) {
|
||||
@ -154,21 +231,16 @@ namespace NekoGui {
|
||||
// profile2 (in) (global) tag g-(id)
|
||||
// profile1 tag (chainTag)-(id)
|
||||
// profile0 (out) tag (chainTag)-(id) / single: chainTag=g-(id)
|
||||
auto tagOut = chainTag + "-" + Int2String(ent->id);
|
||||
|
||||
// needGlobal: can only contain one?
|
||||
bool needGlobal = false;
|
||||
auto tagOut = chainTag + "-" + Int2String(ent->id) + "-" + Int2String(index);
|
||||
|
||||
// first profile set as global
|
||||
auto isFirstProfile = index == ents.length() - 1;
|
||||
if (isFirstProfile) {
|
||||
needGlobal = true;
|
||||
tagOut = "g-" + Int2String(ent->id);
|
||||
tagOut = "g-" + Int2String(ent->id) + "-" + Int2String(index);
|
||||
}
|
||||
|
||||
// last profile set as "proxy"
|
||||
if (chainId == 0 && index == 0) {
|
||||
needGlobal = false;
|
||||
tagOut = "proxy";
|
||||
}
|
||||
|
||||
@ -177,13 +249,6 @@ namespace NekoGui {
|
||||
status->result->ignoreConnTag << tagOut;
|
||||
}
|
||||
|
||||
if (needGlobal) {
|
||||
if (status->globalProfiles.contains(ent->id)) {
|
||||
continue;
|
||||
}
|
||||
status->globalProfiles += ent->id;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
// chain rules: past
|
||||
if (pastExternalStat == 0) {
|
||||
@ -433,7 +498,7 @@ namespace NekoGui {
|
||||
}
|
||||
|
||||
// Outbounds
|
||||
auto tagProxy = BuildChain(0, status);
|
||||
auto tagProxy = BuildChain(status->chainID, status);
|
||||
if (!status->result->error.isEmpty()) return;
|
||||
|
||||
// direct & block & dns-out
|
||||
@ -465,7 +530,7 @@ namespace NekoGui {
|
||||
if (dataStore->spmode_vpn) {
|
||||
routeObj["auto_detect_interface"] = true;
|
||||
}
|
||||
routeObj["final"] = dataStore->routing->def_outbound;
|
||||
if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound;
|
||||
|
||||
auto routeChain = NekoGui::profileManager->GetRouteChain(NekoGui::dataStore->routing->current_route_id);
|
||||
if (routeChain == nullptr) {
|
||||
|
||||
@ -16,16 +16,23 @@ namespace NekoGui {
|
||||
std::list<std::shared_ptr<NekoGui_fmt::ExternalBuildResult>> extRs;
|
||||
};
|
||||
|
||||
class BuildTestConfigResult {
|
||||
public:
|
||||
QString error;
|
||||
QMap<int, QString> fullConfigs;
|
||||
QMap<QString, int> tag2entID;
|
||||
QJsonObject coreConfig;
|
||||
QStringList outboundTags;
|
||||
};
|
||||
|
||||
class BuildConfigStatus {
|
||||
public:
|
||||
std::shared_ptr<BuildConfigResult> result;
|
||||
std::shared_ptr<ProxyEntity> ent;
|
||||
int chainID = 0;
|
||||
bool forTest;
|
||||
bool forExport;
|
||||
|
||||
// priv
|
||||
QList<int> globalProfiles;
|
||||
|
||||
// xxList is V2Ray format string list
|
||||
|
||||
QStringList domainListDNSDirect;
|
||||
@ -37,7 +44,9 @@ namespace NekoGui {
|
||||
QJsonArray outbounds;
|
||||
};
|
||||
|
||||
std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport);
|
||||
std::shared_ptr<BuildTestConfigResult> BuildTestConfig(QList<std::shared_ptr<ProxyEntity>> profiles);
|
||||
|
||||
std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport, int chainID = 0);
|
||||
|
||||
void BuildConfigSingBox(const std::shared_ptr<BuildConfigStatus> &status);
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Mahdi-zarei/sing-box-extra/boxbox"
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
"strings"
|
||||
@ -13,12 +14,9 @@ import (
|
||||
"grpc_server/gen"
|
||||
|
||||
"github.com/Mahdi-zarei/sing-box-extra/boxapi"
|
||||
"github.com/Mahdi-zarei/sing-box-extra/boxbox"
|
||||
"github.com/Mahdi-zarei/sing-box-extra/boxmain"
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
"github.com/matsuridayo/libneko/neko_log"
|
||||
"github.com/matsuridayo/libneko/speedtest"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -86,53 +84,66 @@ func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) Test(ctx context.Context, in *gen.TestReq) (out *gen.TestResp, _ error) {
|
||||
func (s *server) Test(ctx context.Context, in *gen.TestReq) (*gen.TestResp, error) {
|
||||
var testInstance *boxbox.Box
|
||||
var cancel context.CancelFunc
|
||||
var err error
|
||||
out = &gen.TestResp{Ms: 0}
|
||||
|
||||
defer func() {
|
||||
var twice = true
|
||||
if in.TestCurrent {
|
||||
if instance == nil {
|
||||
return &gen.TestResp{Results: []*gen.URLTestResp{{
|
||||
OutboundTag: "proxy",
|
||||
LatencyMs: 0,
|
||||
Error: "Instance is not running",
|
||||
}}}, nil
|
||||
}
|
||||
testInstance = instance
|
||||
twice = false
|
||||
} else {
|
||||
testInstance, cancel, err = boxmain.Create([]byte(in.Config), true)
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
return nil, err
|
||||
}
|
||||
}()
|
||||
|
||||
if in.Mode == gen.TestMode_UrlTest {
|
||||
var i *boxbox.Box
|
||||
var cancel context.CancelFunc
|
||||
if in.Config != nil {
|
||||
// Test instance
|
||||
i, cancel, err = boxmain.Create([]byte(in.Config.CoreConfig), true)
|
||||
if i != nil {
|
||||
defer i.Close()
|
||||
defer cancel()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Test running instance
|
||||
i = instance
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Latency
|
||||
out.Ms, err = speedtest.UrlTest(boxapi.CreateProxyHttpClient(i), in.Url, in.Timeout)
|
||||
} else if in.Mode == gen.TestMode_TcpPing {
|
||||
out.Ms, err = speedtest.TcpPing(in.Address, in.Timeout)
|
||||
} else if in.Mode == gen.TestMode_FullTest {
|
||||
i, cancel, err := boxmain.Create([]byte(in.Config.CoreConfig), true)
|
||||
if i != nil {
|
||||
defer i.Close()
|
||||
defer cancel()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return grpc_server.DoFullTest(ctx, in, i)
|
||||
defer cancel()
|
||||
defer testInstance.Close()
|
||||
}
|
||||
|
||||
return
|
||||
outboundTags := in.OutboundTags
|
||||
if in.UseDefaultOutbound || in.TestCurrent {
|
||||
outbound, err := testInstance.Router().DefaultOutbound("tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outboundTags = []string{outbound.Tag()}
|
||||
}
|
||||
|
||||
var maxConcurrency = in.MaxConcurrency
|
||||
if maxConcurrency >= 500 || maxConcurrency == 0 {
|
||||
maxConcurrency = MaxConcurrentTests
|
||||
}
|
||||
results := BatchURLTest(testCtx, testInstance, outboundTags, in.Url, int(maxConcurrency), twice)
|
||||
|
||||
res := make([]*gen.URLTestResp, 0)
|
||||
for idx, data := range results {
|
||||
errStr := ""
|
||||
if data.Error != nil {
|
||||
errStr = data.Error.Error()
|
||||
}
|
||||
res = append(res, &gen.URLTestResp{
|
||||
OutboundTag: outboundTags[idx],
|
||||
LatencyMs: int32(data.Duration.Milliseconds()),
|
||||
Error: errStr,
|
||||
})
|
||||
}
|
||||
|
||||
return &gen.TestResp{Results: res}, nil
|
||||
}
|
||||
|
||||
func (s *server) StopTest(ctx context.Context, in *gen.EmptyReq) (*gen.EmptyResp, error) {
|
||||
cancelTests()
|
||||
testCtx, cancelTests = context.WithCancel(context.Background())
|
||||
|
||||
return &gen.EmptyResp{}, nil
|
||||
}
|
||||
|
||||
func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
_ "unsafe"
|
||||
|
||||
@ -14,6 +15,7 @@ func main() {
|
||||
fmt.Println("sing-box:", boxbox.Version)
|
||||
fmt.Println()
|
||||
|
||||
testCtx, cancelTests = context.WithCancel(context.Background())
|
||||
grpc_server.RunCore(setupCore, &server{})
|
||||
return
|
||||
}
|
||||
|
||||
99
go/cmd/nekobox_core/test_utils.go
Normal file
99
go/cmd/nekobox_core/test_utils.go
Normal file
@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/Mahdi-zarei/sing-box-extra/boxbox"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var testCtx context.Context
|
||||
var cancelTests context.CancelFunc
|
||||
|
||||
const URLTestTimeout = 3 * time.Second
|
||||
const MaxConcurrentTests = 100
|
||||
|
||||
type URLTestResult struct {
|
||||
Duration time.Duration
|
||||
Error error
|
||||
}
|
||||
|
||||
func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url string, maxConcurrency int, twice bool) []*URLTestResult {
|
||||
router := i.Router()
|
||||
resMap := make(map[string]*URLTestResult)
|
||||
resAccess := sync.Mutex{}
|
||||
limiter := make(chan struct{}, maxConcurrency)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(outboundTags))
|
||||
for _, tag := range outboundTags {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
resAccess.Lock()
|
||||
resMap[tag] = &URLTestResult{
|
||||
Duration: 0,
|
||||
Error: errors.New("test aborted"),
|
||||
}
|
||||
resAccess.Unlock()
|
||||
default:
|
||||
time.Sleep(2 * time.Millisecond) // don't spawn goroutines too quickly
|
||||
limiter <- struct{}{}
|
||||
go func(t string) {
|
||||
defer wg.Done()
|
||||
outbound, found := router.Outbound(t)
|
||||
if !found {
|
||||
panic("no outbound with tag " + t + " found")
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, network string, addr string) (net.Conn, error) {
|
||||
return outbound.DialContext(ctx, "tcp", metadata.ParseSocksaddr(addr))
|
||||
},
|
||||
},
|
||||
Timeout: URLTestTimeout,
|
||||
}
|
||||
// to properly measure muxed configs, let's do the test twice
|
||||
duration, err := urlTest(ctx, client, url)
|
||||
if err == nil && twice {
|
||||
duration, err = urlTest(ctx, client, url)
|
||||
}
|
||||
resAccess.Lock()
|
||||
resMap[t] = &URLTestResult{
|
||||
Duration: duration,
|
||||
Error: err,
|
||||
}
|
||||
resAccess.Unlock()
|
||||
<-limiter
|
||||
}(tag)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
res := make([]*URLTestResult, 0, len(outboundTags))
|
||||
for _, tag := range outboundTags {
|
||||
res = append(res, resMap[tag])
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func urlTest(ctx context.Context, client *http.Client, url string) (time.Duration, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, URLTestTimeout)
|
||||
defer cancel()
|
||||
begin := time.Now()
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
return time.Since(begin), nil
|
||||
}
|
||||
@ -1,180 +0,0 @@
|
||||
package grpc_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"grpc_server/gen"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
"github.com/matsuridayo/libneko/speedtest"
|
||||
)
|
||||
|
||||
const (
|
||||
KiB = 1024
|
||||
MiB = 1024 * KiB
|
||||
)
|
||||
|
||||
func getBetweenStr(str, start, end string) string {
|
||||
n := strings.Index(str, start)
|
||||
if n == -1 {
|
||||
n = 0
|
||||
}
|
||||
str = string([]byte(str)[n:])
|
||||
m := strings.Index(str, end)
|
||||
if m == -1 {
|
||||
m = len(str)
|
||||
}
|
||||
str = string([]byte(str)[:m])
|
||||
return str[len(start):]
|
||||
}
|
||||
|
||||
func DoFullTest(ctx context.Context, in *gen.TestReq, instance interface{}) (out *gen.TestResp, _ error) {
|
||||
out = &gen.TestResp{}
|
||||
httpClient := neko_common.CreateProxyHttpClient(instance)
|
||||
|
||||
// Latency
|
||||
var latency string
|
||||
if in.FullLatency {
|
||||
t, _ := speedtest.UrlTest(httpClient, in.Url, in.Timeout)
|
||||
out.Ms = t
|
||||
if t > 0 {
|
||||
latency = fmt.Sprint(t, "ms")
|
||||
} else {
|
||||
latency = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// UDP Latency
|
||||
var udpLatency string
|
||||
if in.FullUdpLatency {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||
result := make(chan string)
|
||||
|
||||
go func() {
|
||||
var startTime = time.Now()
|
||||
pc, err := neko_common.DialContext(ctx, instance, "udp", "8.8.8.8:53")
|
||||
if err == nil {
|
||||
defer pc.Close()
|
||||
dnsPacket, _ := hex.DecodeString("0000010000010000000000000377777706676f6f676c6503636f6d0000010001")
|
||||
_, err = pc.Write(dnsPacket)
|
||||
if err == nil {
|
||||
var buf [1400]byte
|
||||
_, err = pc.Read(buf[:])
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
var endTime = time.Now()
|
||||
result <- fmt.Sprint(endTime.Sub(startTime).Abs().Milliseconds(), "ms")
|
||||
} else {
|
||||
log.Println("UDP Latency test error:", err)
|
||||
result <- "Error"
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
udpLatency = "Timeout"
|
||||
case r := <-result:
|
||||
udpLatency = r
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
|
||||
// 入口 IP
|
||||
var in_ip string
|
||||
if in.FullInOut {
|
||||
_in_ip, err := net.ResolveIPAddr("ip", in.InAddress)
|
||||
if err == nil {
|
||||
in_ip = _in_ip.String()
|
||||
} else {
|
||||
in_ip = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// 出口 IP
|
||||
var out_ip string
|
||||
if in.FullInOut {
|
||||
resp, err := httpClient.Get("https://www.cloudflare.com/cdn-cgi/trace")
|
||||
if err == nil {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
out_ip = getBetweenStr(string(b), "ip=", "\n")
|
||||
resp.Body.Close()
|
||||
} else {
|
||||
out_ip = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// 下载
|
||||
var speed string
|
||||
if in.FullSpeed {
|
||||
if in.FullSpeedTimeout <= 0 {
|
||||
in.FullSpeedTimeout = 30
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(in.FullSpeedTimeout))
|
||||
result := make(chan string)
|
||||
var bodyClose io.Closer
|
||||
|
||||
go func() {
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", in.FullSpeedUrl, nil)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err == nil && resp != nil && resp.Body != nil {
|
||||
bodyClose = resp.Body
|
||||
defer resp.Body.Close()
|
||||
|
||||
timeStart := time.Now()
|
||||
n, _ := io.Copy(io.Discard, resp.Body)
|
||||
timeEnd := time.Now()
|
||||
|
||||
duration := math.Max(timeEnd.Sub(timeStart).Seconds(), 0.000001)
|
||||
resultSpeed := (float64(n) / duration) / MiB
|
||||
result <- fmt.Sprintf("%.2fMiB/s", resultSpeed)
|
||||
} else {
|
||||
result <- "Error"
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
speed = "Timeout"
|
||||
case s := <-result:
|
||||
speed = s
|
||||
}
|
||||
|
||||
cancel()
|
||||
if bodyClose != nil {
|
||||
bodyClose.Close()
|
||||
}
|
||||
}
|
||||
|
||||
fr := make([]string, 0)
|
||||
if latency != "" {
|
||||
fr = append(fr, fmt.Sprintf("Latency: %s", latency))
|
||||
}
|
||||
if udpLatency != "" {
|
||||
fr = append(fr, fmt.Sprintf("UDPLatency: %s", udpLatency))
|
||||
}
|
||||
if speed != "" {
|
||||
fr = append(fr, fmt.Sprintf("Speed: %s", speed))
|
||||
}
|
||||
if in_ip != "" {
|
||||
fr = append(fr, fmt.Sprintf("In: %s", in_ip))
|
||||
}
|
||||
if out_ip != "" {
|
||||
fr = append(fr, fmt.Sprintf("Out: %s", out_ip))
|
||||
}
|
||||
|
||||
out.FullReport = strings.Join(fr, " / ")
|
||||
|
||||
return
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ service LibcoreService {
|
||||
rpc Start(LoadConfigReq) returns (ErrorResp) {}
|
||||
rpc Stop(EmptyReq) returns (ErrorResp) {}
|
||||
rpc Test(TestReq) returns (TestResp) {}
|
||||
rpc StopTest(EmptyReq) returns (EmptyResp);
|
||||
rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {}
|
||||
rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {}
|
||||
//
|
||||
@ -36,37 +37,23 @@ message LoadConfigReq {
|
||||
bool disable_stats = 4;
|
||||
}
|
||||
|
||||
enum TestMode {
|
||||
TcpPing = 0;
|
||||
UrlTest = 1;
|
||||
FullTest = 2;
|
||||
message URLTestResp {
|
||||
string outbound_tag = 1;
|
||||
int32 latency_ms = 2;
|
||||
string error = 3;
|
||||
}
|
||||
|
||||
message TestReq {
|
||||
TestMode mode = 1;
|
||||
int32 timeout = 6;
|
||||
// TcpPing
|
||||
string address = 2;
|
||||
// UrlTest
|
||||
LoadConfigReq config = 3;
|
||||
string inbound = 4;
|
||||
string url = 5;
|
||||
// FullTest
|
||||
string in_address = 7;
|
||||
bool full_latency = 8;
|
||||
bool full_speed = 9;
|
||||
string full_speed_url = 13;
|
||||
int32 full_speed_timeout = 14;
|
||||
bool full_in_out = 10;
|
||||
bool full_udp_latency = 12;
|
||||
//
|
||||
bool full_nat = 11 [deprecated = true];
|
||||
string config = 1;
|
||||
repeated string outbound_tags = 2;
|
||||
bool use_default_outbound = 3;
|
||||
string url = 4;
|
||||
bool test_current = 5;
|
||||
int32 max_concurrency = 6;
|
||||
}
|
||||
|
||||
message TestResp {
|
||||
string error = 1;
|
||||
int32 ms = 2;
|
||||
string full_report = 3;
|
||||
repeated URLTestResp results = 1;
|
||||
}
|
||||
|
||||
message QueryStatsReq{
|
||||
|
||||
@ -24,6 +24,7 @@ const (
|
||||
LibcoreService_Start_FullMethodName = "/libcore.LibcoreService/Start"
|
||||
LibcoreService_Stop_FullMethodName = "/libcore.LibcoreService/Stop"
|
||||
LibcoreService_Test_FullMethodName = "/libcore.LibcoreService/Test"
|
||||
LibcoreService_StopTest_FullMethodName = "/libcore.LibcoreService/StopTest"
|
||||
LibcoreService_QueryStats_FullMethodName = "/libcore.LibcoreService/QueryStats"
|
||||
LibcoreService_ListConnections_FullMethodName = "/libcore.LibcoreService/ListConnections"
|
||||
LibcoreService_GetGeoIPList_FullMethodName = "/libcore.LibcoreService/GetGeoIPList"
|
||||
@ -42,6 +43,7 @@ type LibcoreServiceClient interface {
|
||||
Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error)
|
||||
StopTest(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error)
|
||||
QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error)
|
||||
ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error)
|
||||
GetGeoIPList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoIPListResponse, error)
|
||||
@ -104,6 +106,15 @@ func (c *libcoreServiceClient) Test(ctx context.Context, in *TestReq, opts ...gr
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) StopTest(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) {
|
||||
out := new(EmptyResp)
|
||||
err := c.cc.Invoke(ctx, LibcoreService_StopTest_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) {
|
||||
out := new(QueryStatsResp)
|
||||
err := c.cc.Invoke(ctx, LibcoreService_QueryStats_FullMethodName, in, out, opts...)
|
||||
@ -176,6 +187,7 @@ type LibcoreServiceServer interface {
|
||||
Start(context.Context, *LoadConfigReq) (*ErrorResp, error)
|
||||
Stop(context.Context, *EmptyReq) (*ErrorResp, error)
|
||||
Test(context.Context, *TestReq) (*TestResp, error)
|
||||
StopTest(context.Context, *EmptyReq) (*EmptyResp, error)
|
||||
QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error)
|
||||
ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error)
|
||||
GetGeoIPList(context.Context, *EmptyReq) (*GetGeoIPListResponse, error)
|
||||
@ -205,6 +217,9 @@ func (UnimplementedLibcoreServiceServer) Stop(context.Context, *EmptyReq) (*Erro
|
||||
func (UnimplementedLibcoreServiceServer) Test(context.Context, *TestReq) (*TestResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Test not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) StopTest(context.Context, *EmptyReq) (*EmptyResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method StopTest not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented")
|
||||
}
|
||||
@ -329,6 +344,24 @@ func _LibcoreService_Test_Handler(srv interface{}, ctx context.Context, dec func
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_StopTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).StopTest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: LibcoreService_StopTest_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).StopTest(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(QueryStatsReq)
|
||||
if err := dec(in); err != nil {
|
||||
@ -482,6 +515,10 @@ var LibcoreService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Test",
|
||||
Handler: _LibcoreService_Test_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "StopTest",
|
||||
Handler: _LibcoreService_StopTest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "QueryStats",
|
||||
Handler: _LibcoreService_QueryStats_Handler,
|
||||
|
||||
@ -91,6 +91,8 @@ func RunCore(setupCore func(), server gen.LibcoreServiceServer) {
|
||||
s := grpc.NewServer(
|
||||
grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auther.Authenticate)),
|
||||
grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auther.Authenticate)),
|
||||
grpc.MaxRecvMsgSize(1024*1024*1024), // 1 gigaByte
|
||||
grpc.MaxSendMsgSize(1024*1024*1024), // 1 gigaByte
|
||||
)
|
||||
gen.RegisterLibcoreServiceServer(s, server)
|
||||
|
||||
|
||||
15
rpc/gRPC.cpp
15
rpc/gRPC.cpp
@ -66,9 +66,7 @@ namespace QtGrpc {
|
||||
QNetworkRequest request(callUrl);
|
||||
// request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
|
||||
// request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
||||
request.setAttribute(QNetworkRequest::Http2DirectAttribute, true);
|
||||
#endif
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String{"application/grpc"});
|
||||
request.setRawHeader("Cache-Control", "no-store");
|
||||
request.setRawHeader(GrpcAcceptEncodingHeader, QByteArray{"identity,deflate,gzip"});
|
||||
@ -268,6 +266,19 @@ namespace NekoGui_rpc {
|
||||
}
|
||||
}
|
||||
|
||||
void Client::StopTests(bool *rpcOK) {
|
||||
const libcore::EmptyReq req;
|
||||
libcore::EmptyResp resp;
|
||||
|
||||
auto status = make_grpc_channel()->Call("StopTest", req, &resp);
|
||||
|
||||
if (status == QNetworkReply::NoError) {
|
||||
*rpcOK = true;
|
||||
} else {
|
||||
NOT_OK
|
||||
}
|
||||
}
|
||||
|
||||
libcore::UpdateResp Client::Update(bool *rpcOK, const libcore::UpdateReq &request) {
|
||||
libcore::UpdateResp reply;
|
||||
auto status = default_grpc_channel->Call("Update", request, &reply);
|
||||
|
||||
@ -28,6 +28,8 @@ namespace NekoGui_rpc {
|
||||
|
||||
libcore::TestResp Test(bool *rpcOK, const libcore::TestReq &request);
|
||||
|
||||
void StopTests(bool *rpcOK);
|
||||
|
||||
libcore::UpdateResp Update(bool *rpcOK, const libcore::UpdateReq &request);
|
||||
|
||||
QStringList GetGeoList(bool *rpcOK, GeoRuleSetType mode);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include "./ui_mainwindow.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "fmt/Preset.hpp"
|
||||
#include "db/ProfileFilter.hpp"
|
||||
#include "db/ConfigBuilder.hpp"
|
||||
#include "sub/GroupUpdater.hpp"
|
||||
@ -68,7 +67,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
}
|
||||
themeManager->ApplyTheme(NekoGui::dataStore->theme);
|
||||
ui->setupUi(this);
|
||||
speedTestThreadPool->setMaxThreadCount(NekoGui::dataStore->test_concurrent);
|
||||
speedTestThreadPool->setMaxThreadCount(10); // constant value
|
||||
//
|
||||
connect(ui->menu_start, &QAction::triggered, this, [=]() { neko_start(); });
|
||||
connect(ui->menu_stop, &QAction::triggered, this, [=]() { neko_stop(); });
|
||||
@ -323,37 +322,28 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
neko_set_spmode_vpn(false);
|
||||
});
|
||||
connect(ui->menu_qr, &QAction::triggered, this, [=]() { display_qr_link(false); });
|
||||
connect(ui->menu_tcp_ping, &QAction::triggered, this, [=]() { speedtest_current_group(0); });
|
||||
connect(ui->menu_url_test, &QAction::triggered, this, [=]() { speedtest_current_group(1); });
|
||||
connect(ui->menu_full_test, &QAction::triggered, this, [=]() { speedtest_current_group(2); });
|
||||
connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { speedtest_current_group(114514); });
|
||||
|
||||
connect(ui->menu_server, &QMenu::aboutToShow, this, [=](){
|
||||
if (!speedtestRunning.tryLock()) {
|
||||
ui->menu_server->addAction(ui->menu_stop_testing);
|
||||
return;
|
||||
} else {
|
||||
speedtestRunning.unlock();
|
||||
ui->menu_server->removeAction(ui->menu_stop_testing);
|
||||
}
|
||||
});
|
||||
connect(ui->actionUrl_Test_Selected, &QAction::triggered, this, [=]() {
|
||||
speedtest_current_group(get_now_selected_list());
|
||||
});
|
||||
connect(ui->actionUrl_Test_Group, &QAction::triggered, this, [=]() {
|
||||
speedtest_current_group(NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder());
|
||||
});
|
||||
connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { stopSpeedTests(); });
|
||||
//
|
||||
auto set_selected_or_group = [=](int mode) {
|
||||
// 0=group 1=select 2=unknown(menu is hide)
|
||||
ui->menu_server->setProperty("selected_or_group", mode);
|
||||
};
|
||||
auto move_tests_to_menu = [=](bool menuCurrent_Select) {
|
||||
return [=] {
|
||||
if (menuCurrent_Select) {
|
||||
ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_tcp_ping);
|
||||
ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_url_test);
|
||||
ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_full_test);
|
||||
ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_stop_testing);
|
||||
ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_clear_test_result);
|
||||
ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_resolve_domain);
|
||||
} else {
|
||||
ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_tcp_ping);
|
||||
ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_url_test);
|
||||
ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_full_test);
|
||||
ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_stop_testing);
|
||||
ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_clear_test_result);
|
||||
ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_resolve_domain);
|
||||
}
|
||||
set_selected_or_group(menuCurrent_Select ? 1 : 0);
|
||||
};
|
||||
};
|
||||
connect(ui->menuCurrent_Select, &QMenu::aboutToShow, this, move_tests_to_menu(true));
|
||||
connect(ui->menuCurrent_Group, &QMenu::aboutToShow, this, move_tests_to_menu(false));
|
||||
connect(ui->menu_server, &QMenu::aboutToHide, this, [=] {
|
||||
setTimeout([=] { set_selected_or_group(2); }, this, 200);
|
||||
});
|
||||
@ -911,11 +901,10 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor
|
||||
ui->proxyListTable->setRowCount(0);
|
||||
// 添加行
|
||||
int row = -1;
|
||||
for (const auto &[id, profile]: NekoGui::profileManager->profiles) {
|
||||
if (NekoGui::dataStore->current_group != profile->gid) continue;
|
||||
for (const auto ent: NekoGui::profileManager->GetGroup(NekoGui::dataStore->current_group)->ProfilesWithOrder()) {
|
||||
row++;
|
||||
ui->proxyListTable->insertRow(row);
|
||||
ui->proxyListTable->row2Id += id;
|
||||
ui->proxyListTable->row2Id += ent->id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -190,9 +190,11 @@ private:
|
||||
|
||||
static void setup_grpc();
|
||||
|
||||
void speedtest_current_group(int mode);
|
||||
void speedtest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles);
|
||||
|
||||
void RunSpeedTest(const std::shared_ptr<NekoGui::ProxyEntity>& ent, int mode, const QStringList& full_test_flags);
|
||||
void stopSpeedTests();
|
||||
|
||||
void RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID = -1);
|
||||
|
||||
void url_test_current();
|
||||
|
||||
|
||||
@ -38,10 +38,10 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -60,10 +60,10 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -82,10 +82,10 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -104,10 +104,10 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -138,7 +138,7 @@
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -160,7 +160,7 @@
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
@ -192,19 +192,19 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="MyTableWidget" name="proxyListTable">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
<enum>QAbstractItemView::ScrollMode::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
@ -274,7 +274,7 @@
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="masterLogBrowser">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>false</bool>
|
||||
@ -300,10 +300,10 @@
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Maximum</enum>
|
||||
<enum>QSizePolicy::Policy::Maximum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -337,7 +337,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>25</height>
|
||||
<height>33</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_program">
|
||||
@ -404,29 +404,6 @@
|
||||
<addaction name="menu_copy_links"/>
|
||||
<addaction name="menu_copy_links_nkr"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuCurrent_Group">
|
||||
<property name="title">
|
||||
<string>Current Group</string>
|
||||
</property>
|
||||
<addaction name="actionfake_5"/>
|
||||
<addaction name="menu_tcp_ping"/>
|
||||
<addaction name="menu_url_test"/>
|
||||
<addaction name="menu_full_test"/>
|
||||
<addaction name="menu_stop_testing"/>
|
||||
<addaction name="menu_clear_test_result"/>
|
||||
<addaction name="menu_resolve_domain"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_remove_unavailable"/>
|
||||
<addaction name="menu_delete_repeat"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_update_subscription"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuCurrent_Select">
|
||||
<property name="title">
|
||||
<string>Current Select</string>
|
||||
</property>
|
||||
<addaction name="actionfake_4"/>
|
||||
</widget>
|
||||
<addaction name="menu_add_from_input"/>
|
||||
<addaction name="menu_add_from_clipboard"/>
|
||||
<addaction name="separator"/>
|
||||
@ -440,12 +417,8 @@
|
||||
<addaction name="menu_delete"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_share_item"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuCurrent_Select"/>
|
||||
<addaction name="menuCurrent_Group"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_profile_debug_info"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionUrl_Test_Selected"/>
|
||||
<addaction name="actionUrl_Test_Group"/>
|
||||
</widget>
|
||||
<addaction name="menu_program"/>
|
||||
<addaction name="menu_preferences"/>
|
||||
@ -779,6 +752,16 @@
|
||||
<string>Stop Testing</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUrl_Test_Selected">
|
||||
<property name="text">
|
||||
<string>Url Test Selected</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUrl_Test_Group">
|
||||
<property name="text">
|
||||
<string>Url Test Group</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@ -8,12 +8,10 @@
|
||||
#include "ui/widget/MessageBoxTimer.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include <QInputDialog>
|
||||
#include <QPushButton>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
// ext core
|
||||
|
||||
@ -49,7 +47,7 @@ void MainWindow::setup_grpc() {
|
||||
runOnNewThread([=] { NekoGui_traffic::trafficLooper->Loop(); });
|
||||
}
|
||||
|
||||
void MainWindow::RunSpeedTest(const std::shared_ptr<NekoGui::ProxyEntity>& ent, int mode, const QStringList& full_test_flags) {
|
||||
void MainWindow::RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap<QString, int>& tag2entID, int entID) {
|
||||
if (stopSpeedtest.load()) {
|
||||
MW_show_log("Profile test aborted");
|
||||
return;
|
||||
@ -59,46 +57,13 @@ void MainWindow::RunSpeedTest(const std::shared_ptr<NekoGui::ProxyEntity>& ent,
|
||||
QSemaphore extSem;
|
||||
|
||||
libcore::TestReq req;
|
||||
req.set_mode((libcore::TestMode) mode);
|
||||
req.set_timeout(3000);
|
||||
req.set_url(NekoGui::dataStore->test_latency_url.toStdString());
|
||||
if (mode == libcore::TestMode::UrlTest || mode == libcore::FullTest) {
|
||||
auto c = BuildConfig(ent, true, false);
|
||||
if (!c->error.isEmpty()) {
|
||||
ent->full_test_report = c->error;
|
||||
ent->Save();
|
||||
auto profileId = ent->id;
|
||||
runOnUiThread([this, profileId] {
|
||||
refresh_proxy_list(profileId);
|
||||
});
|
||||
}
|
||||
//
|
||||
if (!c->extRs.empty()) {
|
||||
runOnUiThread(
|
||||
[&] {
|
||||
extCs = CreateExtCFromExtR(c->extRs, true);
|
||||
QThread::msleep(500);
|
||||
extSem.release();
|
||||
},
|
||||
DS_cores);
|
||||
extSem.acquire();
|
||||
}
|
||||
//
|
||||
auto config = new libcore::LoadConfigReq;
|
||||
config->set_core_config(QJsonObject2QString(c->coreConfig, true).toStdString());
|
||||
req.set_allocated_config(config);
|
||||
req.set_in_address(ent->bean->serverAddress.toStdString());
|
||||
|
||||
req.set_full_latency(full_test_flags.contains("1"));
|
||||
req.set_full_udp_latency(full_test_flags.contains("2"));
|
||||
req.set_full_speed(full_test_flags.contains("3"));
|
||||
req.set_full_in_out(full_test_flags.contains("4"));
|
||||
|
||||
req.set_full_speed_url(NekoGui::dataStore->test_download_url.toStdString());
|
||||
req.set_full_speed_timeout(NekoGui::dataStore->test_download_timeout);
|
||||
} else if (mode == libcore::TcpPing) {
|
||||
req.set_address(ent->bean->DisplayAddress().toStdString());
|
||||
for (const auto &item: outboundTags) {
|
||||
req.add_outbound_tags(item.toStdString());
|
||||
}
|
||||
req.set_config(config.toStdString());
|
||||
req.set_url(NekoGui::dataStore->test_latency_url.toStdString());
|
||||
req.set_use_default_outbound(useDefault);
|
||||
req.set_max_concurrency(NekoGui::dataStore->test_concurrent);
|
||||
|
||||
bool rpcOK;
|
||||
auto result = defaultClient->Test(&rpcOK, req);
|
||||
@ -117,29 +82,33 @@ void MainWindow::RunSpeedTest(const std::shared_ptr<NekoGui::ProxyEntity>& ent,
|
||||
//
|
||||
if (!rpcOK) return;
|
||||
|
||||
if (result.error().empty()) {
|
||||
ent->latency = result.ms();
|
||||
if (ent->latency == 0) ent->latency = 1; // nekoray use 0 to represents not tested
|
||||
} else {
|
||||
ent->latency = -1;
|
||||
}
|
||||
ent->full_test_report = result.full_report().c_str(); // higher priority
|
||||
ent->Save();
|
||||
for (const auto &res: result.results()) {
|
||||
if (!tag2entID.empty()) {
|
||||
entID = tag2entID.count(QString(res.outbound_tag().c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag().c_str())];
|
||||
}
|
||||
if (entID == -1) {
|
||||
MW_show_log("Something is very wrong, the subject ent cannot be found!");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result.error().empty()) {
|
||||
MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), result.error().c_str()));
|
||||
}
|
||||
auto ent = NekoGui::profileManager->GetProfile(entID);
|
||||
if (ent == nullptr) {
|
||||
MW_show_log("Profile manager data is corrupted, try again.");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto profileId = ent->id;
|
||||
runOnUiThread([this, profileId] {
|
||||
refresh_proxy_list(profileId);
|
||||
});
|
||||
if (res.error().empty()) {
|
||||
ent->latency = res.latency_ms();
|
||||
} else {
|
||||
ent->latency = 0;
|
||||
MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error().c_str()));
|
||||
}
|
||||
ent->Save();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::speedtest_current_group(int mode) {
|
||||
// menu_stop_testing mode == 114514, TODO use proper constants
|
||||
if (mode == 114514) {
|
||||
stopSpeedtest.store(true);
|
||||
void MainWindow::speedtest_current_group(const QList<std::shared_ptr<NekoGui::ProxyEntity>>& profiles) {
|
||||
if (profiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!speedtestRunning.tryLock()) {
|
||||
@ -147,95 +116,72 @@ void MainWindow::speedtest_current_group(int mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
speedTestThreadPool->setMaxThreadCount(NekoGui::dataStore->test_concurrent);
|
||||
runOnNewThread([this, profiles]() {
|
||||
auto buildObject = NekoGui::BuildTestConfig(profiles);
|
||||
|
||||
auto profiles = get_selected_or_group();
|
||||
if (profiles.isEmpty()) {
|
||||
speedtestRunning.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList full_test_flags;
|
||||
if (mode == libcore::FullTest) {
|
||||
auto w = new QDialog(this);
|
||||
auto layout = new QVBoxLayout(w);
|
||||
w->setWindowTitle(tr("Test Options"));
|
||||
//
|
||||
auto l1 = new QCheckBox(tr("Latency"));
|
||||
auto l2 = new QCheckBox(tr("UDP latency"));
|
||||
auto l3 = new QCheckBox(tr("Download speed"));
|
||||
auto l4 = new QCheckBox(tr("In and Out IP"));
|
||||
//
|
||||
auto box = new QDialogButtonBox;
|
||||
box->setOrientation(Qt::Horizontal);
|
||||
box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
connect(box, &QDialogButtonBox::accepted, w, &QDialog::accept);
|
||||
connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);
|
||||
//
|
||||
layout->addWidget(l1);
|
||||
layout->addWidget(l2);
|
||||
layout->addWidget(l3);
|
||||
layout->addWidget(l4);
|
||||
layout->addWidget(box);
|
||||
if (w->exec() != QDialog::Accepted) {
|
||||
w->deleteLater();
|
||||
speedtestRunning.unlock();
|
||||
return;
|
||||
}
|
||||
//
|
||||
if (l1->isChecked()) full_test_flags << "1";
|
||||
if (l2->isChecked()) full_test_flags << "2";
|
||||
if (l3->isChecked()) full_test_flags << "3";
|
||||
if (l4->isChecked()) full_test_flags << "4";
|
||||
//
|
||||
w->deleteLater();
|
||||
if (full_test_flags.isEmpty()) {
|
||||
speedtestRunning.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
runOnNewThread([profiles, full_test_flags, mode, this]() {
|
||||
std::atomic<int> counter(0);
|
||||
stopSpeedtest.store(false);
|
||||
for (const auto &item: profiles) {
|
||||
auto func = [&item, full_test_flags, mode, this, &counter, profiles]() {
|
||||
MainWindow::RunSpeedTest(item, mode, full_test_flags);
|
||||
auto testCount = buildObject->fullConfigs.size() + (!buildObject->outboundTags.empty());
|
||||
for (const auto &entID: buildObject->fullConfigs.keys()) {
|
||||
auto configStr = buildObject->fullConfigs[entID];
|
||||
auto func = [this, &counter, testCount, configStr, entID]() {
|
||||
MainWindow::RunSpeedTest(configStr, true, {}, {}, entID);
|
||||
counter++;
|
||||
if (counter.load() == profiles.size()) {
|
||||
if (counter.load() == testCount) {
|
||||
speedtestRunning.unlock();
|
||||
}
|
||||
};
|
||||
speedTestThreadPool->start(func);
|
||||
}
|
||||
|
||||
if (!buildObject->outboundTags.empty()) {
|
||||
auto func = [this, &buildObject, &counter, testCount]() {
|
||||
MainWindow::RunSpeedTest(QJsonObject2QString(buildObject->coreConfig, false), false, buildObject->outboundTags, buildObject->tag2entID);
|
||||
counter++;
|
||||
if (counter.load() == testCount) {
|
||||
speedtestRunning.unlock();
|
||||
}
|
||||
};
|
||||
speedTestThreadPool->start(func);
|
||||
}
|
||||
|
||||
speedtestRunning.lock();
|
||||
speedtestRunning.unlock();
|
||||
MW_show_log("Speedtest finished!");
|
||||
runOnUiThread([=]{
|
||||
refresh_proxy_list();
|
||||
MW_show_log("Speedtest finished!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::stopSpeedTests() {
|
||||
bool ok;
|
||||
defaultClient->StopTests(&ok);
|
||||
|
||||
if (!ok) {
|
||||
MW_show_log("Failed to stop tests");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::url_test_current() {
|
||||
last_test_time = QTime::currentTime();
|
||||
ui->label_running->setText(tr("Testing"));
|
||||
|
||||
runOnNewThread([=] {
|
||||
libcore::TestReq req;
|
||||
req.set_mode(libcore::UrlTest);
|
||||
req.set_timeout(3000);
|
||||
req.set_test_current(true);
|
||||
req.set_url(NekoGui::dataStore->test_latency_url.toStdString());
|
||||
|
||||
bool rpcOK;
|
||||
auto result = defaultClient->Test(&rpcOK, req);
|
||||
if (!rpcOK) return;
|
||||
|
||||
auto latency = result.ms();
|
||||
auto latency = result.results()[0].latency_ms();
|
||||
last_test_time = QTime::currentTime();
|
||||
|
||||
runOnUiThread([=] {
|
||||
if (!result.error().empty()) {
|
||||
MW_show_log(QString("UrlTest error: %1").arg(result.error().c_str()));
|
||||
if (!result.results()[0].error().empty()) {
|
||||
MW_show_log(QString("UrlTest error: %1").arg(result.results()[0].error().c_str()));
|
||||
}
|
||||
if (latency <= 0) {
|
||||
ui->label_running->setText(tr("Test Result") + ": " + tr("Unavailable"));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user