diff --git a/Makefile b/Makefile
index db6028b..eac5388 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-RELEASE = v1.1.2
+RELEASE = v1.2
BUILD_COMMIT := $(shell git rev-parse --short HEAD)
DATE := $(shell date -u '+%F %X UTC')
VERSION := ${RELEASE}, rev ${BUILD_COMMIT}, compiled at ${DATE}
diff --git a/cmd/nocc-daemon/main.go b/cmd/nocc-daemon/main.go
index 951c4a9..9e9e791 100644
--- a/cmd/nocc-daemon/main.go
+++ b/cmd/nocc-daemon/main.go
@@ -91,17 +91,20 @@ func main() {
}
if *checkServersAndExit {
- if len(remoteNoccHosts) == 0 {
- failedStart("no remote hosts set; you should set NOCC_SERVERS or NOCC_SERVERS_FILENAME")
- }
if len(os.Args) == 3 { // nocc -check-servers {remoteHostPort}
remoteNoccHosts = []string{os.Args[2]}
}
+ if len(remoteNoccHosts) == 0 {
+ failedStart("no remote hosts set; you should set NOCC_SERVERS or NOCC_SERVERS_FILENAME")
+ }
client.RequestRemoteStatus(remoteNoccHosts)
os.Exit(0)
}
if *dumpServerLogsAndExit {
+ if len(os.Args) == 3 { // nocc -dump-server-logs {remoteHostPort}
+ remoteNoccHosts = []string{os.Args[2]}
+ }
if len(remoteNoccHosts) == 0 {
failedStart("no remote hosts set; you should set NOCC_SERVERS or NOCC_SERVERS_FILENAME")
}
diff --git a/cmd/nocc-server/main.go b/cmd/nocc-server/main.go
index c22b023..4dc3ff9 100644
--- a/cmd/nocc-server/main.go
+++ b/cmd/nocc-server/main.go
@@ -4,7 +4,7 @@ import (
"fmt"
"net"
"os"
- "path"
+ "runtime"
"time"
"github.com/VKCOM/nocc/internal/common"
@@ -18,24 +18,29 @@ func failedStart(message string, err error) {
os.Exit(1)
}
-// cleanupWorkingDir ensures that workingDir exists and is empty
+// prepareEmptyDir ensures that serverDir exists and is empty
// it's executed on server launch
// as a consequence, all file caches are lost on restart
-func cleanupWorkingDir(workingDir string) error {
- oldWorkingDir := workingDir + ".old"
-
- if err := os.RemoveAll(oldWorkingDir); err != nil {
- failedStart("can't remove old working dir", err)
- }
- if _, err := os.Stat(workingDir); err == nil {
- if err := os.Rename(workingDir, oldWorkingDir); err != nil {
- failedStart("can't rename working dir %s to .old", err)
+func prepareEmptyDir(parentDir *string, subdir string) string {
+ // if /tmp/nocc/cpp/src-cache already exists, it means, that it contains files from a previous launch
+ // to start up as quickly as possible, do the following:
+ // 1) rename it to /tmp/nocc/cpp/src-cache.old
+ // 2) clear it recursively in the background
+ serverDir := *parentDir + "/" + subdir
+ if _, err := os.Stat(serverDir); err == nil {
+ oldDirRenamed := fmt.Sprintf("%s.old.%d", serverDir, time.Now().Unix())
+ if err := os.Rename(serverDir, oldDirRenamed); err != nil {
+ failedStart("can't rename "+serverDir, err)
}
+ go func() {
+ _ = os.RemoveAll(oldDirRenamed)
+ }()
}
- if err := os.MkdirAll(workingDir, os.ModePerm); err != nil {
- return err
+
+ if err := os.MkdirAll(serverDir, os.ModePerm); err != nil {
+ failedStart("can't create "+serverDir, err)
}
- return nil
+ return serverDir
}
// printDockerContainerIP is a dev/debug function called only when build special for local Docker, for local testing.
@@ -58,8 +63,10 @@ func main() {
"host", "")
listenPort := common.CmdEnvInt("Listening port, default 43210.", 43210,
"port", "")
- workingDir := common.CmdEnvString("Directory for saving incoming files, default /tmp/nocc-server.", "/tmp/nocc-server",
- "working-dir", "")
+ cppStoreDir := common.CmdEnvString("Directory for incoming C++ files and src cache, default /tmp/nocc/cpp.\nIt can be placed in tmpfs to speed up compilation", "/tmp/nocc/cpp",
+ "cpp-dir", "")
+ objStoreDir := common.CmdEnvString("Directory for resulting obj files and obj cache, default /tmp/nocc/obj.", "/tmp/nocc/obj",
+ "obj-dir", "")
logFileName := common.CmdEnvString("A filename to log, by default use stderr.", "",
"log-filename", "")
logVerbosity := common.CmdEnvInt("Logger verbosity level for INFO (-1 off, default 0, max 2).\nErrors are logged always.", 0,
@@ -70,6 +77,8 @@ func main() {
"obj-cache-limit", "")
statsdHostPort := common.CmdEnvString("Statsd udp address (host:port), omitted by default.\nIf omitted, stats won't be written.", "",
"statsd", "")
+ maxParallelCxx := common.CmdEnvInt("Max amount of C++ compiler processes launched in parallel, other ready sessions are waiting in a queue.\nBy default, it's a number of CPUs on the current machine.", int64(runtime.NumCPU()),
+ "max-parallel-cxx", "")
common.ParseCmdFlagsCombiningWithEnv()
@@ -78,10 +87,6 @@ func main() {
os.Exit(0)
}
- if err = cleanupWorkingDir(*workingDir); err != nil {
- failedStart("Can't create working directory "+*workingDir, err)
- }
-
if err = server.MakeLoggerServer(*logFileName, *logVerbosity); err != nil {
failedStart("Can't init logger", err)
}
@@ -95,12 +100,12 @@ func main() {
failedStart("Failed to connect to statsd", err)
}
- s.ActiveClients, err = server.MakeClientsStorage(path.Join(*workingDir, "clients"))
+ s.ActiveClients, err = server.MakeClientsStorage(prepareEmptyDir(cppStoreDir, "clients"))
if err != nil {
failedStart("Failed to init clients hashtable", err)
}
- s.CxxLauncher, err = server.MakeCxxLauncher()
+ s.CxxLauncher, err = server.MakeCxxLauncher(*maxParallelCxx)
if err != nil {
failedStart("Failed to init cxx launcher", err)
}
@@ -110,17 +115,17 @@ func main() {
failedStart("Failed to init system headers hashtable", err)
}
- s.SrcFileCache, err = server.MakeSrcFileCache(path.Join(*workingDir, "src-cache"), *srcCacheLimit)
+ s.SrcFileCache, err = server.MakeSrcFileCache(prepareEmptyDir(cppStoreDir, "src-cache"), *srcCacheLimit)
if err != nil {
failedStart("Failed to init src file cache", err)
}
- s.ObjFileCache, err = server.MakeObjFileCache(path.Join(*workingDir, "obj-cache"), *objCacheLimit)
+ s.ObjFileCache, err = server.MakeObjFileCache(prepareEmptyDir(objStoreDir, "obj-cache"), prepareEmptyDir(objStoreDir, "cxx-out"), *objCacheLimit)
if err != nil {
failedStart("Failed to init obj file cache", err)
}
- s.PchCompilation, err = server.MakePchCompilation(path.Join(*workingDir, "pch"))
+ s.PchCompilation, err = server.MakePchCompilation(prepareEmptyDir(cppStoreDir, "pch"))
if err != nil {
failedStart("Failed to init pch compilation", err)
}
diff --git a/cmd/nocc.cpp b/cmd/nocc.cpp
index 3af2e08..4fae7a9 100644
--- a/cmd/nocc.cpp
+++ b/cmd/nocc.cpp
@@ -44,7 +44,7 @@ char *format_time_to_log() {
static char time_buf[64];
time_t ts = time(nullptr);
tm *now = localtime(&ts);
- sprintf(time_buf, "%d/%02d/%02d %02d:%02d:%02d", 1900 + now->tm_year, 1 + now->tm_mon, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);
+ sprintf(time_buf, "%d-%02d-%02d %02d:%02d:%02d", 1900 + now->tm_year, 1 + now->tm_mon, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);
return time_buf;
}
@@ -97,13 +97,6 @@ void __attribute__((noreturn)) execute_cxx_locally(const char *errToPrint, int e
exit(1);
}
-void __attribute__((noreturn)) execute_distcc_locally() {
- ARGV[0] = strdup("distcc");
- execvp("distcc", ARGV + 0);
- printf("could not run `distcc`, exit(1)\n");
- exit(1);
-}
-
void __attribute__((noreturn)) execute_go_nocc_instead_of_cpp() {
execv(NOCC_GO_EXECUTABLE, ARGV);
printf("could not run %s, exit(1)\n", NOCC_GO_EXECUTABLE);
@@ -279,13 +272,6 @@ int main(int argc, char *argv[]) {
exit(1);
}
- // this possible fallback will be available for some time just in case
- char *env_fallback_to_distcc = getenv("NOCC_FALLBACK_TO_DISTCC");
- bool fallback_to_distcc = env_fallback_to_distcc != nullptr && env_fallback_to_distcc[0] == '1';
- if (fallback_to_distcc) {
- execute_distcc_locally();
- }
-
if (ARGC == 2 && !strcmp(ARGV[1], "start")) {
int sockfd = connect_to_go_daemon_or_start_a_new_one();
exit(sockfd == -1 ? 1 : 0);
diff --git a/docs/configuration.md b/docs/configuration.md
index 92355be..98d5903 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -34,16 +34,18 @@ When you launch lots of jobs like `make -j 600`, then `nocc-daemon` has to maint
All configuration on a server-side is done using command-line arguments.
For a server, they are more reliable than environment variables.
-| Cmd argument | Description |
-|--------------------------|-----------------------------------------------------------------------------------------|
-| `-host {string}` | Binding address, default 0.0.0.0. |
-| `-port {int}` | Listening port, default 43210. |
-| `-working-dir {string}` | Directory for saving incoming files, default */tmp/nocc-server*. |
-| `-log-filename {string}` | A filename to log, by default use stderr. |
-| `-log-verbosity {int}` | Logger verbosity level for INFO (-1 off, default 0, max 2). Errors are logged always. |
-| `-src-cache-limit {int}` | Header and source cache limit, in bytes, default 4G. |
-| `-obj-cache-limit {int}` | Compiled obj cache limit, in bytes, default 16G. |
-| `-statsd {string}` | Statsd udp address (host:port), omitted by default. If omitted, stats won't be written. |
+| Cmd argument | Description |
+|---------------------------|-----------------------------------------------------------------------------------------|
+| `-host {string}` | Binding address, default 0.0.0.0. |
+| `-port {int}` | Listening port, default 43210. |
+| `-cpp-dir {string}` | Directory for incoming C++ files and src cache, default */tmp/nocc/cpp*. |
+| `-obj-dir {string}` | Directory for resulting obj files and obj cache, default */tmp/nocc/obj*. |
+| `-log-filename {string}` | A filename to log, by default use stderr. |
+| `-log-verbosity {int}` | Logger verbosity level for INFO (-1 off, default 0, max 2). Errors are logged always. |
+| `-src-cache-limit {int}` | Header and source cache limit, in bytes, default 4G. |
+| `-obj-cache-limit {int}` | Compiled obj cache limit, in bytes, default 16G. |
+| `-statsd {string}` | Statsd udp address (host:port), omitted by default. If omitted, stats won't be written. |
+| `-max-parallel-cxx {int}` | Max amount of C++ compiler processes launched in parallel, default *nCPU*. |
All file caches are lost on restart, as references to files are kept in memory.
There is also an LRU expiration mechanism to fit cache limits.
@@ -75,6 +77,26 @@ A list of all written stats could be obtained [inside statsd.go](../internal/ser
They are quite intuitive, that's why we don't duplicate them here.
+
+
+## Configuring nocc + tmpfs
+
+The directory passed as `-cpp-dir` can be placed in **tmpfs**.
+All operations with cpp files are performed in that directory:
+* incoming files (h/cpp/etc.) are saved there mirroring client's file structure;
+* src-cache is placed there;
+* pch files are placed there;
+* tmp files for preventing race conditions are also there, not in sys tmp dir.
+
+So, if that directory is placed in tmpfs, the C++ compiler will take all files from memory (except for system headers),
+which noticeably speeds up compilation.
+
+When setting up limits to tmpfs in a system, ensure that it will fit `-src-cache-limit` plus some extra space.
+
+Note, that placing `-obj-dir` in tmpfs is not recommended, because obj files are usually much heavier,
+and they are just transparently streamed back from a hard disk in chunks.
+
+
## Other commands from a client
diff --git a/internal/client/compile-remotely.go b/internal/client/compile-remotely.go
index 356806c..a41f58e 100644
--- a/internal/client/compile-remotely.go
+++ b/internal/client/compile-remotely.go
@@ -52,7 +52,7 @@ func CompileCppRemotely(daemon *Daemon, cwd string, invocation *Invocation, remo
return 0, nil, nil, err
}
- logClient.Info(1, "remote", remote.remoteHostPort, "sessionID", invocation.sessionID, "waiting", len(fileIndexesToUpload), "uploads", invocation.cppInFile)
+ logClient.Info(1, "remote", remote.remoteHost, "sessionID", invocation.sessionID, "waiting", len(fileIndexesToUpload), "uploads", invocation.cppInFile)
logClient.Info(2, "checked", len(requiredFiles), "files whether upload is needed or they exist on remote")
invocation.summary.AddTiming("remote_session")
@@ -75,7 +75,7 @@ func CompileCppRemotely(daemon *Daemon, cwd string, invocation *Invocation, remo
// Now, we have a resulting .o file placed in a path determined by -o from command line.
if exitCode != 0 {
- logClient.Info(0, "remote C++ compiler exited with code", exitCode, "sessionID", invocation.sessionID, invocation.cppInFile, remote.remoteHostPort)
+ logClient.Info(0, "remote C++ compiler exited with code", exitCode, "sessionID", invocation.sessionID, invocation.cppInFile, remote.remoteHost)
logClient.Info(1, "cxxExitCode:", exitCode, "sessionID", invocation.sessionID, "\ncxxStdout:", strings.TrimSpace(string(invocation.cxxStdout)), "\ncxxStderr:", strings.TrimSpace(string(invocation.cxxStderr)))
} else {
logClient.Info(2, "saved obj file to", invocation.objOutFile)
diff --git a/internal/client/daemon.go b/internal/client/daemon.go
index 3a1f0d5..47cec35 100644
--- a/internal/client/daemon.go
+++ b/internal/client/daemon.go
@@ -19,7 +19,7 @@ import (
)
const (
- timeoutForceInterruptInvocation = 5 * time.Minute
+ timeoutForceInterruptInvocation = 8 * time.Minute
)
// Daemon is created once, in a separate process `nocc-daemon`, which is listening for connections via unix socket.
@@ -37,6 +37,7 @@ type Daemon struct {
listener *DaemonUnixSockListener
remoteConnections []*RemoteConnection
+ allRemotesDelim string
localCxxThrottle chan struct{}
disableObjCache bool
@@ -77,7 +78,18 @@ func detectHostUserName() string {
return curUser.Username
}
-func MakeDaemon(remoteNoccHosts []string, disableObjCache bool, disableOwnIncludes bool, localCxxQueueSize int64) (*Daemon, error) {
+func MakeDaemon(remoteNoccHosts []string, disableObjCache bool, disableOwnIncludes bool, maxLocalCxxProcesses int64) (*Daemon, error) {
+ // send env NOCC_SERVERS on connect everywhere
+ // this is for debugging purpose: in production, all clients should have the same servers list
+ // to ensure this, just grep server logs: only one unique string should appear
+ allRemotesDelim := ""
+ for _, remoteHostPort := range remoteNoccHosts {
+ if allRemotesDelim != "" {
+ allRemotesDelim += ","
+ }
+ allRemotesDelim += ExtractRemoteHostWithoutPort(remoteHostPort)
+ }
+
// env NOCC_SERVERS and others are supposed to be the same between `nocc` invocations
// (in practice, this is true, as the first `nocc` invocation has no precedence over any other in a bunch)
daemon := &Daemon{
@@ -85,23 +97,36 @@ func MakeDaemon(remoteNoccHosts []string, disableObjCache bool, disableOwnInclud
quitChan: make(chan int),
clientID: detectClientID(),
hostUserName: detectHostUserName(),
- remoteConnections: make([]*RemoteConnection, 0, len(remoteNoccHosts)),
- localCxxThrottle: make(chan struct{}, localCxxQueueSize),
+ remoteConnections: make([]*RemoteConnection, len(remoteNoccHosts)),
+ allRemotesDelim: allRemotesDelim,
+ localCxxThrottle: make(chan struct{}, maxLocalCxxProcesses),
disableOwnIncludes: disableOwnIncludes,
disableObjCache: disableObjCache,
- disableLocalCxx: localCxxQueueSize == 0,
+ disableLocalCxx: maxLocalCxxProcesses == 0,
activeInvocations: make(map[uint32]*Invocation, 300),
includesCache: make(map[string]*IncludesCache, 1),
}
- for _, remoteHostPort := range remoteNoccHosts {
- remote, err := MakeRemoteConnection(daemon, remoteHostPort, 1, 1)
- if err != nil {
- remote.isUnavailable = true
- logClient.Error("error connecting to", remoteHostPort, err)
- }
- daemon.remoteConnections = append(daemon.remoteConnections, remote)
+ // connect to all remotes in parallel
+ wg := sync.WaitGroup{}
+ wg.Add(len(remoteNoccHosts))
+
+ ctxConnect, cancelFunc := context.WithTimeout(context.Background(), 5000*time.Millisecond)
+ defer cancelFunc()
+
+ for index, remoteHostPort := range remoteNoccHosts {
+ go func(index int, remoteHostPort string) {
+ remote, err := MakeRemoteConnection(daemon, remoteHostPort, ctxConnect)
+ if err != nil {
+ remote.isUnavailable = true
+ logClient.Error("error connecting to", remoteHostPort, err)
+ }
+
+ daemon.remoteConnections[index] = remote
+ wg.Done()
+ }(index, remoteHostPort)
}
+ wg.Wait()
return daemon, nil
}
@@ -202,10 +227,10 @@ func (daemon *Daemon) HandleInvocation(req DaemonSockRequest) DaemonSockResponse
}
remote := daemon.chooseRemoteConnectionForCppCompilation(invocation.cppInFile)
- invocation.summary.remoteHostPort = remote.remoteHostPort
+ invocation.summary.remoteHost = remote.remoteHost
if remote.isUnavailable {
- return daemon.FallbackToLocalCxx(req, fmt.Errorf("remote %s is unavailable", remote.remoteHostPort))
+ return daemon.FallbackToLocalCxx(req, fmt.Errorf("remote %s is unavailable", remote.remoteHost))
}
daemon.mu.Lock()
@@ -292,7 +317,7 @@ func (daemon *Daemon) PeriodicallyInterruptHangedInvocations() {
daemon.mu.Lock()
for _, invocation := range daemon.activeInvocations {
if time.Since(invocation.createTime) > timeoutForceInterruptInvocation {
- invocation.ForceInterrupt(fmt.Errorf("interrupt sessionID %d after %d sec timeout", invocation.sessionID, int(time.Since(invocation.createTime).Seconds())))
+ invocation.ForceInterrupt(fmt.Errorf("interrupt sessionID %d (%s) after %d sec timeout", invocation.sessionID, invocation.summary.remoteHost, int(time.Since(invocation.createTime).Seconds())))
}
}
daemon.mu.Unlock()
diff --git a/internal/client/files-receiving.go b/internal/client/files-receiving.go
index a6e14a8..195647c 100644
--- a/internal/client/files-receiving.go
+++ b/internal/client/files-receiving.go
@@ -153,7 +153,7 @@ func receiveObjFileByChunks(stream pb.CompilationService_RecvCompiledObjStreamCl
return errWrite, false
}
- fileTmp, errWrite := common.OpenTempFile(objOutFile, false)
+ fileTmp, errWrite := common.OpenTempFile(objOutFile)
if errWrite == nil {
_, errWrite = fileTmp.Write(firstChunk.ChunkBody)
}
diff --git a/internal/client/invocation-summary.go b/internal/client/invocation-summary.go
index 2707359..59740d8 100644
--- a/internal/client/invocation-summary.go
+++ b/internal/client/invocation-summary.go
@@ -17,7 +17,7 @@ type invocationTimingItem struct {
// It's mostly for developing/debugging purposes: multiple nocc invocations are appended to a single log file,
// from which we can compute statistics, average and percentiles, either in total or partitioned by hosts.
type InvocationSummary struct {
- remoteHostPort string
+ remoteHost string
nIncludes int
nFilesSent int
@@ -44,7 +44,7 @@ func (s *InvocationSummary) ToLogString(invocation *Invocation) string {
b := strings.Builder{}
fmt.Fprintf(&b, "cppInFile=%q, remote=%s, sessionID=%d, nIncludes=%d, nFilesSent=%d, nBytesSent=%d, nBytesReceived=%d, cxxDuration=%dms",
- invocation.cppInFile, s.remoteHostPort, invocation.sessionID, s.nIncludes, s.nFilesSent, s.nBytesSent, s.nBytesReceived, invocation.cxxDuration)
+ invocation.cppInFile, s.remoteHost, invocation.sessionID, s.nIncludes, s.nFilesSent, s.nBytesSent, s.nBytesReceived, invocation.cxxDuration)
prevTime := invocation.createTime
fmt.Fprintf(&b, ", started=0ms")
diff --git a/internal/client/invocation.go b/internal/client/invocation.go
index 5c88ca9..1893ba6 100644
--- a/internal/client/invocation.go
+++ b/internal/client/invocation.go
@@ -143,7 +143,7 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
return
} else if arg == "-isysroot" {
// an exception for local development when "remote" is also local, but generally unsupported yet
- if len(daemon.remoteConnections) == 1 && daemon.remoteConnections[0].remoteHostPort == "127.0.0.1:43210" {
+ if len(daemon.remoteConnections) == 1 && daemon.remoteConnections[0].remoteHost == "127.0.0.1" {
invocation.cxxArgs = append(invocation.cxxArgs, arg, cmdLine[i+1])
i++
continue
@@ -259,7 +259,7 @@ func (invocation *Invocation) DoneUploadFile(err error) {
}
func (invocation *Invocation) ForceInterrupt(err error) {
- logClient.Error("force interrupt", "sessionID", invocation.sessionID, invocation.cppInFile, err)
+ logClient.Error("force interrupt", "sessionID", invocation.sessionID, "remoteHost", invocation.summary.remoteHost, invocation.cppInFile, err)
// release invocation.wgUpload
for atomic.LoadInt32(&invocation.waitUploads) != 0 {
invocation.DoneUploadFile(err)
diff --git a/internal/client/manage-servers.go b/internal/client/manage-servers.go
index 3249ee8..637b4dd 100644
--- a/internal/client/manage-servers.go
+++ b/internal/client/manage-servers.go
@@ -7,7 +7,6 @@ import (
"strings"
"time"
- "github.com/VKCOM/nocc/internal/common"
"github.com/VKCOM/nocc/pb"
)
@@ -63,6 +62,10 @@ func requestRemoteDumpLogsOne(remoteHostPort string, dumpToFolder string, resCha
defer grpcClient.Clear()
stream, err := grpcClient.pb.DumpLogs(grpcClient.callContext, &pb.DumpLogsRequest{})
+ if err != nil {
+ resChannel <- rpcDumpLogsRes{err: err, remoteHostPort: remoteHostPort}
+ return
+ }
bytesReceived := make([]int, 0)
for {
@@ -75,7 +78,7 @@ func requestRemoteDumpLogsOne(remoteHostPort string, dumpToFolder string, resCha
break
}
- logOutFile := path.Join(dumpToFolder, strings.Split(remoteHostPort, ":")[0]+firstChunk.LogFileExt)
+ logOutFile := path.Join(dumpToFolder, ExtractRemoteHostWithoutPort(remoteHostPort)+firstChunk.LogFileExt)
nBytes, err := receiveLogFileByChunks(stream, firstChunk, logOutFile)
if err != nil {
resChannel <- rpcDumpLogsRes{err: err, remoteHostPort: remoteHostPort}
@@ -122,70 +125,84 @@ func RequestRemoteStatus(remoteNoccHosts []string) {
nOk := 0
nTotal := len(remoteNoccHosts)
- uniqueVersions := make(map[string]int)
- uniqueArgs := make(map[string]int)
- uniqueGcc := make(map[string]int)
- uniqueClang := make(map[string]int)
+ noccVersionsByRemote := make(map[string][]string)
+ noccServerArgsByRemote := make(map[string][]string)
+ gccVersionsByRemote := make(map[string][]string)
+ clangVersionsByRemote := make(map[string][]string)
+ ulimitByRemote := make(map[string][]string)
+ unameByRemote := make(map[string][]string)
+
+ addByRemote := func(mapByRemote map[string][]string, key string, remoteHost string) {
+ if _, ok := mapByRemote[key]; !ok {
+ mapByRemote[key] = make([]string, 0)
+ }
+ mapByRemote[key] = append(mapByRemote[key], remoteHost)
+ }
for range remoteNoccHosts {
res := <-resChannel
- var reply *pb.StatusReply = res.reply
+ var r *pb.StatusReply = res.reply
+ remoteHost := ExtractRemoteHostWithoutPort(res.remoteHostPort)
if res.err != nil {
- fmt.Printf("Server \033[36m%s\033[0m unavailable: %v\n", res.remoteHostPort, res.err)
+ fmt.Printf("Server \033[36m%s\033[0m \033[31munavailable\033[0m: %v\n", remoteHost, res.err)
continue
}
- fmt.Printf("Server \033[36m%s\033[0m \u001B[32mok\u001B[0m (uptime %s)\n", res.remoteHostPort, time.Duration(reply.ServerUptime).Truncate(time.Second))
- fmt.Println(" Processing time:", res.processingTime.Truncate(time.Microsecond))
- fmt.Println(" Log file size KB:", reply.LogFileSize/1024)
- fmt.Println(" Src cache size KB:", reply.SrcCacheSize/1024)
- fmt.Println(" Obj cache size KB:", reply.ObjCacheSize/1024)
- fmt.Println(" nocc-server version:", reply.ServerVersion)
- fmt.Println(" nocc-server cmd args:", reply.ServerArgs)
- fmt.Println(" g++:", reply.GccVersion)
- fmt.Println(" clang:", reply.ClangVersion)
- if reply.ServerVersion != common.GetVersion() {
- fmt.Println("\033[36mnocc-server version differs from current client\033[0m")
+ fmt.Printf("Server \033[36m%s\033[0m \033[32mok\033[0m (uptime %s)\n", remoteHost, time.Duration(r.ServerUptime).Truncate(time.Second))
+ fmt.Printf(" Processing time: %d ms\n", res.processingTime.Milliseconds())
+ fmt.Printf(" Disk consumption: log %d KB, src cache %d KB, obj cache %d KB\n", r.LogFileSize/1024, r.SrcCacheSize/1024, r.ObjCacheSize/1024)
+ fmt.Printf(" Activity: sessions total %d, active %d\n", r.SessionsTotal, r.SessionsActive)
+ fmt.Printf(" Cxx: calls %d, more10sec %d, more30sec %d\n", r.CxxCalls, r.CxxDurMore10Sec, r.CxxDurMore30Sec)
+
+ if len(r.UniqueRemotes) > 1 {
+ fmt.Printf(" \033[31mnon-unique remotes\033[0m:\n")
+ for _, u := range r.UniqueRemotes {
+ fmt.Printf(" %s\n", u)
+ }
}
nOk++
- uniqueVersions[reply.ServerVersion]++
- uniqueArgs[strings.Join(reply.ServerArgs, " ")]++
- uniqueGcc[reply.GccVersion]++
- uniqueClang[reply.ClangVersion]++
+ addByRemote(noccVersionsByRemote, r.ServerVersion, remoteHost)
+ addByRemote(noccServerArgsByRemote, strings.Join(r.ServerArgs, " "), remoteHost)
+ addByRemote(gccVersionsByRemote, r.GccVersion, remoteHost)
+ addByRemote(clangVersionsByRemote, r.ClangVersion, remoteHost)
+ addByRemote(ulimitByRemote, fmt.Sprintf("ulimit %d", r.ULimit), remoteHost)
+ addByRemote(unameByRemote, r.UName, remoteHost)
}
- if len(remoteNoccHosts) == 1 {
+ if len(remoteNoccHosts) == 1 || nOk == 0 {
return
}
+ printEqualOfDiff := func(mapByRemote map[string][]string, msgAllEqual string, msgDiff string) {
+ if len(mapByRemote) == 1 {
+ var firstKey string
+ for k := range mapByRemote {
+ firstKey = k
+ break
+ }
+ fmt.Printf(" %s:\n %s\n", msgAllEqual, firstKey)
+ return
+ }
+ fmt.Printf("\033[31m %s\033[0m\n", msgDiff)
+ for k, hosts := range mapByRemote {
+ fmt.Printf(" * \033[1m%s\033[0m\n %s\n", k, strings.Join(hosts, ", "))
+ }
+ }
+
fmt.Printf("\033[1mSummary:\033[00m\n")
if nOk == nTotal {
fmt.Printf("\033[32m ok %d / %d\033[0m\n", nOk, nTotal)
} else {
fmt.Printf("\033[31m ok %d / %d\033[0m\n", nOk, nTotal)
}
- if len(uniqueVersions) == 1 {
- fmt.Println(" all nocc versions are the same")
- } else {
- fmt.Println("\033[31m different nocc versions\033[0m\n ", uniqueVersions)
- }
- if len(uniqueArgs) == 1 {
- fmt.Println(" all nocc cmd args are the same")
- } else {
- fmt.Println("\033[31m different nocc cmd args\033[0m\n ", uniqueArgs)
- }
- if len(uniqueGcc) == 1 {
- fmt.Println(" all g++ versions are the same")
- } else {
- fmt.Println("\033[31m different g++ versions\033[0m\n ", uniqueGcc)
- }
- if len(uniqueClang) == 1 {
- fmt.Println(" all clang versions are the same")
- } else {
- fmt.Println("\033[31m different clang versions\033[0m\n ", uniqueClang)
- }
+ printEqualOfDiff(noccVersionsByRemote, "nocc versions equal", "different nocc versions")
+ printEqualOfDiff(noccServerArgsByRemote, "nocc cmd args equal", "different nocc cmd args")
+ printEqualOfDiff(gccVersionsByRemote, "g++ versions equal", "different g++ versions")
+ printEqualOfDiff(clangVersionsByRemote, "clang versions equal", "different clang versions")
+ printEqualOfDiff(ulimitByRemote, "ulimit equal", "different ulimit")
+ printEqualOfDiff(unameByRemote, "uname equal", "different uname")
}
// RequestRemoteDumpLogs sends the rpc /DumpLogs request for all hosts
@@ -206,9 +223,10 @@ func RequestRemoteDumpLogs(remoteNoccHosts []string, dumpToFolder string) {
for range remoteNoccHosts {
res := <-resChannel
+ remoteHost := ExtractRemoteHostWithoutPort(res.remoteHostPort)
if res.err != nil {
- fmt.Printf("Server \033[36m%s\033[0m unavailable: %v\n", res.remoteHostPort, res.err)
+ fmt.Printf("Server \033[36m%s\033[0m unavailable: %v\n", remoteHost, res.err)
continue
}
@@ -216,7 +234,7 @@ func RequestRemoteDumpLogs(remoteNoccHosts []string, dumpToFolder string) {
for _, nBytes := range res.bytesReceived {
strBytes += fmt.Sprintf("%d ", nBytes)
}
- fmt.Printf("Server \033[36m%s\033[0m dumped %d files (%sbytes)\n", res.remoteHostPort, len(res.bytesReceived), strBytes)
+ fmt.Printf("Server \033[36m%s\033[0m dumped %d files (%sbytes)\n", remoteHost, len(res.bytesReceived), strBytes)
nOk++
}
@@ -241,13 +259,14 @@ func RequestDropAllCaches(remoteNoccHosts []string) {
for range remoteNoccHosts {
res := <-resChannel
var reply *pb.DropAllCachesReply = res.reply
+ remoteHost := ExtractRemoteHostWithoutPort(res.remoteHostPort)
if res.err != nil {
- fmt.Printf("Server \033[36m%s\033[0m unavailable: %v\n", res.remoteHostPort, res.err)
+ fmt.Printf("Server \033[36m%s\033[0m unavailable: %v\n", remoteHost, res.err)
continue
}
- fmt.Printf("Server \033[36m%s\033[0m dropped %d src files and %d obj files\n", res.remoteHostPort, reply.DroppedSrcFiles, reply.DroppedObjFiles)
+ fmt.Printf("Server \033[36m%s\033[0m dropped %d src files and %d obj files\n", remoteHost, reply.DroppedSrcFiles, reply.DroppedObjFiles)
nOk++
}
diff --git a/internal/client/remote-connection.go b/internal/client/remote-connection.go
index 37b394e..80f2ddf 100644
--- a/internal/client/remote-connection.go
+++ b/internal/client/remote-connection.go
@@ -3,7 +3,7 @@ package client
import (
"context"
"fmt"
- "time"
+ "strings"
"github.com/VKCOM/nocc/internal/common"
"github.com/VKCOM/nocc/pb"
@@ -16,6 +16,7 @@ import (
// then all invocations that should be sent to that remote are executed locally within a daemon.
type RemoteConnection struct {
remoteHostPort string
+ remoteHost string // for console output and logs, just IP is more pretty
isUnavailable bool
grpcClient *GRPCClient
@@ -27,11 +28,20 @@ type RemoteConnection struct {
disableObjCache bool
}
-func MakeRemoteConnection(daemon *Daemon, remoteHostPort string, uploadStreamsCount int64, receiveStreamsCount int64) (*RemoteConnection, error) {
+func ExtractRemoteHostWithoutPort(remoteHostPort string) (remoteHost string) {
+ remoteHost = remoteHostPort
+ if idx := strings.Index(remoteHostPort, ":"); idx != -1 {
+ remoteHost = remoteHostPort[:idx]
+ }
+ return
+}
+
+func MakeRemoteConnection(daemon *Daemon, remoteHostPort string, ctxWithTimeout context.Context) (*RemoteConnection, error) {
grpcClient, err := MakeGRPCClient(remoteHostPort)
remote := &RemoteConnection{
remoteHostPort: remoteHostPort,
+ remoteHost: ExtractRemoteHostWithoutPort(remoteHostPort),
grpcClient: grpcClient,
filesUploading: MakeFilesUploading(daemon, grpcClient),
filesReceiving: MakeFilesReceiving(daemon, grpcClient),
@@ -44,29 +54,23 @@ func MakeRemoteConnection(daemon *Daemon, remoteHostPort string, uploadStreamsCo
return remote, err
}
- ctxConnect, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second)
- defer cancelFunc()
-
- _, err = grpcClient.pb.StartClient(ctxConnect, &pb.StartClientRequest{
+ _, err = grpcClient.pb.StartClient(ctxWithTimeout, &pb.StartClientRequest{
ClientID: daemon.clientID,
HostUserName: daemon.hostUserName,
ClientVersion: common.GetVersion(),
DisableObjCache: daemon.disableObjCache,
+ AllRemotesDelim: daemon.allRemotesDelim, // just to log on a server-side
})
if err != nil {
return remote, err
}
- for i := 0; i < int(uploadStreamsCount); i++ {
- if err := remote.filesUploading.CreateUploadStream(); err != nil {
- return remote, err
- }
+ if err := remote.filesUploading.CreateUploadStream(); err != nil {
+ return remote, err
}
- for i := 0; i < int(receiveStreamsCount); i++ {
- if err := remote.filesReceiving.CreateReceiveStream(); err != nil {
- return remote, err
- }
+ if err := remote.filesReceiving.CreateReceiveStream(); err != nil {
+ return remote, err
}
return remote, nil
@@ -78,7 +82,7 @@ func MakeRemoteConnection(daemon *Daemon, remoteHostPort string, uploadStreamsCo
// As an output, the remote responds with files that are missing and needed to be uploaded.
func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation, cwd string, requiredFiles []*pb.FileMetadata) ([]uint32, error) {
if remote.isUnavailable {
- return nil, fmt.Errorf("remote %s is unavailable", remote.remoteHostPort)
+ return nil, fmt.Errorf("remote %s is unavailable", remote.remoteHost)
}
startSessionReply, err := remote.grpcClient.pb.StartCompilationSession(
diff --git a/internal/common/filesystem.go b/internal/common/filesystem.go
index ce889dc..6e62f2f 100644
--- a/internal/common/filesystem.go
+++ b/internal/common/filesystem.go
@@ -1,9 +1,11 @@
package common
import (
+ "math/rand"
"os"
"path"
"path/filepath"
+ "strconv"
)
func MkdirForFile(fileName string) error {
@@ -13,14 +15,9 @@ func MkdirForFile(fileName string) error {
return nil
}
-func OpenTempFile(fullPath string, mkdir bool) (f *os.File, err error) {
- directory, fileName := filepath.Split(fullPath)
- if mkdir {
- if err := os.MkdirAll(directory, os.ModePerm); err != nil {
- return nil, err
- }
- }
- return os.CreateTemp(directory, fileName)
+func OpenTempFile(fullPath string) (f *os.File, err error) {
+ fileNameTmp := fullPath + "." + strconv.Itoa(rand.Int())
+ return os.OpenFile(fileNameTmp, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.ModePerm)
}
func ReplaceFileExt(fileName string, newExt string) string {
diff --git a/internal/common/own-pch-files.go b/internal/common/own-pch-files.go
index 146ec03..49a6a24 100644
--- a/internal/common/own-pch-files.go
+++ b/internal/common/own-pch-files.go
@@ -93,8 +93,9 @@ func (ownPch *OwnPch) CalcPchHash() {
}
}
+// SaveToOwnPchFile is invoked on the client side to create a .nocc-pch file.
func (ownPch *OwnPch) SaveToOwnPchFile() (int64, error) {
- f, err := OpenTempFile(ownPch.OwnPchFile, false)
+ f, err := OpenTempFile(ownPch.OwnPchFile)
if err != nil {
return 0, err
}
diff --git a/internal/server/client.go b/internal/server/client.go
index ff4193e..2a66b37 100644
--- a/internal/server/client.go
+++ b/internal/server/client.go
@@ -25,7 +25,7 @@ const (
// which are saved into a folder with relative paths equal to absolute client paths.
//
// For example, a client uploads 3 files: /home/alice/1.cpp, /home/alice/1.h, /usr/include/math.h.
-// They are saved to /tmp/nocc-server/clients/{clientID}/home/alice/1.cpp and so on.
+// They are saved to /tmp/nocc/cpp/clients/{clientID}/home/alice/1.cpp and so on.
// (if math.h is equal to a server system include /usr/include/math.h, it isn't requested to be uploaded).
//
// fileInClientDir also represents files in the process of uploading, before actually saved to a disk (state field).
@@ -48,12 +48,13 @@ type fileInClientDir struct {
// Every client as a workingDir, where all files uploaded from that client are saved to.
type Client struct {
clientID string
- workingDir string // /tmp/nocc-server/clients/{clientID}
+ workingDir string // /tmp/nocc/cpp/clients/{clientID}
lastSeen time.Time // to detect when a client becomes inactive
mu sync.RWMutex
sessions map[uint32]*Session
files map[string]*fileInClientDir // from clientFileName to a server file
+ dirs map[string]bool // not to call MkdirAll for every file, key is path.Dir(serverFileName)
chanDisconnected chan struct{}
chanReadySessions chan *Session
@@ -71,7 +72,7 @@ func (client *Client) makeNewFile(clientFileName string, fileSize int64, fileSHA
}
// MapClientFileNameToServerAbs converts a client file name to an absolute path on server.
-// For example, /proj/1.cpp maps to /tmp/nocc-server/clients/{clientID}/proj/1.cpp.
+// For example, /proj/1.cpp maps to /tmp/nocc/cpp/clients/{clientID}/proj/1.cpp.
// Note, that system files like /usr/local/include are required to be equal on both sides.
// (if not, a server session will fail to start, and a client will fall back to local compilation)
func (client *Client) MapClientFileNameToServerAbs(clientFileName string) string {
@@ -85,58 +86,53 @@ func (client *Client) MapClientFileNameToServerAbs(clientFileName string) string
}
// MapServerAbsToClientFileName converts an absolute path on server relatively to the client working dir.
-// For example, /tmp/nocc-server/clients/{clientID}/proj/1.cpp maps to /proj/1.cpp.
+// For example, /tmp/nocc/cpp/clients/{clientID}/proj/1.cpp maps to /proj/1.cpp.
// If serverFileName is /usr/local/include, it's left as is.
func (client *Client) MapServerAbsToClientFileName(serverFileName string) string {
return strings.TrimPrefix(serverFileName, client.workingDir)
}
-func (client *Client) StartNewSession(in *pb.StartCompilationSessionRequest) (*Session, error) {
+func (client *Client) CreateNewSession(in *pb.StartCompilationSessionRequest) (*Session, error) {
newSession := &Session{
- sessionID: in.SessionID,
- files: make([]*fileInClientDir, len(in.RequiredFiles)),
- cxxName: in.CxxName,
- cppInFile: in.CppInFile, // as specified in a client cmd line invocation (relative to in.Cwd or abs on a client file system)
- objOutFile: os.TempDir() + fmt.Sprintf("/%s.%d.%s.o", client.clientID, in.SessionID, path.Base(in.CppInFile)),
- client: client,
+ sessionID: in.SessionID,
+ files: make([]*fileInClientDir, len(in.RequiredFiles)),
+ cxxName: in.CxxName,
+ cppInFile: in.CppInFile, // as specified in a client cmd line invocation (relative to in.Cwd or abs on a client file system)
+ client: client,
+ // objOutFile is filled only in cxx is required to be called, see Session.PrepareServerCxxCmdLine()
}
- // old clients that don't send this field (they send abs cppInFile)
- // todo delete later, after upgrading all clients
- if in.Cwd == "" {
- newSession.cxxCwd = client.workingDir
- newSession.cppInFile = client.MapClientFileNameToServerAbs(newSession.cppInFile)
- } else {
- newSession.cxxCwd = client.MapClientFileNameToServerAbs(in.Cwd)
- }
-
- newSession.cxxCmdLine = newSession.PrepareServerCxxCmdLine(in.CxxArgs, in.CxxIDirs)
-
for index, meta := range in.RequiredFiles {
fileSHA256 := common.SHA256{B0_7: meta.SHA256_B0_7, B8_15: meta.SHA256_B8_15, B16_23: meta.SHA256_B16_23, B24_31: meta.SHA256_B24_31}
file, err := client.StartUsingFileInSession(meta.ClientFileName, meta.FileSize, fileSHA256)
newSession.files[index] = file
// the only reason why a session can't be created is a dependency conflict:
- // an old hanging session that is still using a previous version of some .h file (so it can't be removed),
- // whereas now a client reports that this .h file has another sha256
+ // previously, a client reported that clientFileName has sha256=v1, and now it sends sha256=v2
if err != nil {
return nil, err
}
}
- client.mu.Lock()
- client.sessions[newSession.sessionID] = newSession
- client.mu.Unlock()
+ // note, that we don't add newSession to client.sessions: it's just created, not registered
+ // (so, it won't be enumerated in a loop inside GetSessionsNotStartedCompilation until registered)
return newSession, nil
}
+func (client *Client) RegisterCreatedSession(session *Session) {
+ client.mu.Lock()
+ client.sessions[session.sessionID] = session
+ client.mu.Unlock()
+}
+
func (client *Client) CloseSession(session *Session) {
client.mu.Lock()
delete(client.sessions, session.sessionID)
client.mu.Unlock()
- _ = os.Remove(session.objOutFile)
+ if !session.objCacheExists { // delete /tmp/nocc/obj/cxx-out/this.o (already hard linked to obj cache)
+ _ = os.Remove(session.objOutFile)
+ }
session.files = nil
}
@@ -159,7 +155,7 @@ func (client *Client) GetActiveSessionsCount() int {
func (client *Client) GetSessionsNotStartedCompilation() []*Session {
sessions := make([]*Session, 0)
client.mu.RLock()
- for _, session := range client.sessions {
+ for _, session := range client.sessions { // loop over registered sessions
if atomic.LoadInt32(&session.compilationStarted) == 0 {
sessions = append(sessions, session)
}
@@ -173,8 +169,7 @@ func (client *Client) GetSessionsNotStartedCompilation() []*Session {
// If it already exists, compare client sha256 with what we have (if equal, don't need to upload this file again).
//
// The only reason why we can return an error here is a dependency conflict:
-// an old hanging session that is still using a previous version of some .h file (so it can't be removed),
-// whereas now a client reports that this .h file has another sha256.
+// previously, a client reported that clientFileName has sha256=v1, and now it sends sha256=v2.
func (client *Client) StartUsingFileInSession(clientFileName string, fileSize int64, fileSHA256 common.SHA256) (*fileInClientDir, error) {
client.mu.RLock()
file := client.files[clientFileName]
@@ -200,34 +195,75 @@ func (client *Client) StartUsingFileInSession(clientFileName string, fileSize in
return file, nil
}
-// IsFileUploadFailed checks whether a file should be re-requested.
-// A "failed" upload means that it was finished with an error, or it lasts too long.
-// A timeout depends on file size: for instance, .nocc-pch files are big, we'll wait for them for a long time
-// (especially when nocc client uploads it to all servers, the network on a client machine suffers).
-func (client *Client) IsFileUploadFailed(file *fileInClientDir) bool {
- if file.state == fsFileStateUploaded {
- return false
+// MkdirAllForSession ensures that all directories for saving files from session exist
+// (they mirror client directory structure in client.workingDir).
+// Instead of calling os.MkdirAll for every uploaded or hard linked file, they are created in advance.
+// Moreover, we need to call os.MkdirAll only once for all files within it (when it appears first time).
+// After this call, every /home/file.h can be saved into /tmp/.../{clientID}/home/file.h.
+func (client *Client) MkdirAllForSession(session *Session) {
+ dirsToCreate := make([]string, 0)
+
+ client.mu.RLock()
+ for _, file := range session.files {
+ lastSlash := len(file.serverFileName) - 1
+ for file.serverFileName[lastSlash] != '/' {
+ lastSlash--
+ }
+ dir := file.serverFileName[0:lastSlash]
+ if exists := client.dirs[dir]; !exists {
+ // session.files (includes order) is often partially sorted, so add fewer duplicates
+ if len(dirsToCreate) == 0 || dirsToCreate[len(dirsToCreate)-1] != dir {
+ dirsToCreate = append(dirsToCreate, dir)
+ }
+ }
}
- if file.state == fsFileStateUploadError {
- return true
+ if exists := client.dirs[session.cxxCwd]; !exists {
+ dirsToCreate = append(dirsToCreate, session.cxxCwd)
}
+ client.mu.RUnlock()
- passedSec := time.Since(file.uploadStartTime).Seconds()
+ if len(dirsToCreate) == 0 {
+ return
+ }
- if file.fileSize > 10*1024*1024 {
- return passedSec > 60
+ for _, dir := range dirsToCreate {
+ if err := os.MkdirAll(dir, os.ModePerm); err != nil {
+ logServer.Error("can't create dir", dir, err)
+ }
+ }
+
+ client.mu.Lock()
+ for _, dir := range dirsToCreate {
+ client.dirs[dir] = true
}
- if file.fileSize > 256*1024 {
- return passedSec > 15
+ client.mu.Unlock()
+}
+
+// IsFileUploadHanged checks whether a file upload lasts too long, and a file should be re-requested.
+// A timeout depends on file size: for instance, .nocc-pch files are big, we'll wait for them for a long time
+// (especially when nocc client uploads it to all servers, the network on a client machine suffers).
+func (client *Client) IsFileUploadHanged(fileWithStateUploading *fileInClientDir) bool {
+ passedSec := time.Since(fileWithStateUploading.uploadStartTime).Seconds()
+
+ if fileWithStateUploading.fileSize > 5*1024*1024 {
+ return passedSec > 60
}
- return passedSec > 5
+ return passedSec > 15
}
func (client *Client) RemoveWorkingDir() {
+ workingDirRenamed := fmt.Sprintf("%s.old.%d", client.workingDir, time.Now().Unix())
+
client.mu.Lock()
- _ = os.RemoveAll(client.workingDir)
+ _ = os.Rename(client.workingDir, workingDirRenamed)
client.files = make(map[string]*fileInClientDir)
client.mu.Unlock()
+
+ go func() {
+ if err := os.RemoveAll(workingDirRenamed); err != nil {
+ logServer.Error("could not remove client working dir", "clientID", client.clientID, workingDirRenamed, err)
+ }
+ }()
}
func (client *Client) FilesCount() int64 {
diff --git a/internal/server/clients-storage.go b/internal/server/clients-storage.go
index 2e49eaa..b1958ae 100644
--- a/internal/server/clients-storage.go
+++ b/internal/server/clients-storage.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
+ "strings"
"sync"
"sync/atomic"
"time"
@@ -15,16 +16,19 @@ type ClientsStorage struct {
table map[string]*Client
mu sync.RWMutex
- clientsDir string // /tmp/nocc-server/clients
+ clientsDir string // /tmp/nocc/cpp/clients
completedCount int64
lastPurgeTime time.Time
+
+ uniqueRemotesList map[string]string
}
func MakeClientsStorage(clientsDir string) (*ClientsStorage, error) {
return &ClientsStorage{
- table: make(map[string]*Client, 1024),
- clientsDir: clientsDir,
+ table: make(map[string]*Client, 1024),
+ clientsDir: clientsDir,
+ uniqueRemotesList: make(map[string]string, 1),
}, nil
}
@@ -50,7 +54,7 @@ func (allClients *ClientsStorage) OnClientConnected(clientID string, disableObjC
}
workingDir := path.Join(allClients.clientsDir, clientID)
- if err := os.MkdirAll(workingDir, os.ModePerm); err != nil {
+ if err := os.Mkdir(workingDir, os.ModePerm); err != nil {
return nil, fmt.Errorf("can't create client working directory: %v", err)
}
@@ -60,8 +64,9 @@ func (allClients *ClientsStorage) OnClientConnected(clientID string, disableObjC
lastSeen: time.Now(),
sessions: make(map[uint32]*Session, 20),
files: make(map[string]*fileInClientDir, 1024),
+ dirs: make(map[string]bool, 100),
chanDisconnected: make(chan struct{}),
- chanReadySessions: make(chan *Session, 100),
+ chanReadySessions: make(chan *Session, 200),
disableObjCache: disableObjCache,
}
@@ -102,7 +107,7 @@ func (allClients *ClientsStorage) DeleteInactiveClients() {
break
}
- logServer.Info(0, "delete inactive client", "clientID", inactiveClient.clientID, "num files", inactiveClient.FilesCount())
+ logServer.Info(0, "delete inactive client", "clientID", inactiveClient.clientID, "num files", inactiveClient.FilesCount(), "; nClients", allClients.ActiveCount()-1)
allClients.DeleteClient(inactiveClient)
}
}
@@ -148,3 +153,33 @@ func (allClients *ClientsStorage) TotalFilesCountInDirs() int64 {
allClients.mu.RUnlock()
return filesCount
}
+
+// IsRemotesListSeenTheFirstTime maintains allClients.uniqueRemotesList.
+// It's mostly for debug purposes — to detect clients with strange NOCC_SERVERS env.
+// Probably, will be deleted in the future.
+func (allClients *ClientsStorage) IsRemotesListSeenTheFirstTime(allRemotesDelim string, clientID string) bool {
+ allClients.mu.RLock()
+ _, exists := allClients.uniqueRemotesList[allRemotesDelim]
+ allClients.mu.RUnlock()
+
+ if !exists {
+ allClients.mu.Lock()
+ allClients.uniqueRemotesList[allRemotesDelim] = clientID
+ allClients.mu.Unlock()
+ }
+
+ return !exists
+}
+
+func (allClients *ClientsStorage) GetUniqueRemotesListInfo() (uniqueInfo []string) {
+ allClients.mu.RLock()
+
+ uniqueInfo = make([]string, 0, len(allClients.uniqueRemotesList))
+ for allRemotesDelim, clientID := range allClients.uniqueRemotesList {
+ nRemotes := strings.Count(allRemotesDelim, ",") + 1
+ uniqueInfo = append(uniqueInfo, fmt.Sprintf("(n=%d) clientID %s : %s", nRemotes, clientID, allRemotesDelim))
+ }
+
+ allClients.mu.RUnlock()
+ return
+}
diff --git a/internal/server/cxx-launcher.go b/internal/server/cxx-launcher.go
index bf71e86..13441e5 100644
--- a/internal/server/cxx-launcher.go
+++ b/internal/server/cxx-launcher.go
@@ -5,45 +5,103 @@ import (
"fmt"
"os"
"os/exec"
- "runtime"
+ "path"
"strings"
"sync/atomic"
"time"
)
type CxxLauncher struct {
- chanToCompile chan *Session
+ serverCxxThrottle chan struct{}
+
+ nSessionsReadyButWaiting int64
+ nSessionsNowCompiling int64
+
+ totalCalls int64
+ totalDurationMs int64
+ more10secCount int64
+ more30secCount int64
+ nonZeroExitCodeCount int64
}
-func MakeCxxLauncher() (*CxxLauncher, error) {
+func MakeCxxLauncher(maxParallelCxxProcesses int64) (*CxxLauncher, error) {
+ if maxParallelCxxProcesses <= 0 {
+ return nil, fmt.Errorf("invalid maxParallelCxxProcesses %d", maxParallelCxxProcesses)
+ }
+
return &CxxLauncher{
- chanToCompile: make(chan *Session, runtime.NumCPU()),
+ serverCxxThrottle: make(chan struct{}, maxParallelCxxProcesses),
}, nil
}
-func (cxxLauncher *CxxLauncher) EnterInfiniteLoopToCompile(noccServer *NoccServer) {
- for session := range cxxLauncher.chanToCompile {
- go cxxLauncher.launchServerCxxForCpp(session, noccServer)
+// LaunchCxxWhenPossible launches the C++ compiler on a server managing a waiting queue.
+// The purpose of a waiting queue is not to over-utilize server resources at peak times.
+// Currently, amount of max parallel C++ processes is an option provided at start up
+// (it other words, it's not dynamic, nocc-server does not try to analyze CPU/memory).
+func (cxxLauncher *CxxLauncher) LaunchCxxWhenPossible(noccServer *NoccServer, session *Session) {
+ atomic.AddInt64(&cxxLauncher.nSessionsReadyButWaiting, 1)
+ cxxLauncher.serverCxxThrottle <- struct{}{} // blocking
+
+ atomic.AddInt64(&cxxLauncher.nSessionsReadyButWaiting, -1)
+ curParallelCount := atomic.AddInt64(&cxxLauncher.nSessionsNowCompiling, 1)
+
+ logServer.Info(1, "launch cxx #", curParallelCount, "sessionID", session.sessionID, "clientID", session.client.clientID, session.cppInFile)
+ cxxLauncher.launchServerCxxForCpp(session, noccServer) // blocking until cxx ends
+
+ atomic.AddInt64(&cxxLauncher.nSessionsNowCompiling, -1)
+ atomic.AddInt64(&cxxLauncher.totalCalls, 1)
+ atomic.AddInt64(&cxxLauncher.totalDurationMs, int64(session.cxxDuration))
+
+ if session.cxxExitCode != 0 {
+ atomic.AddInt64(&cxxLauncher.nonZeroExitCodeCount, 1)
+ } else if session.cxxDuration > 30000 {
+ atomic.AddInt64(&cxxLauncher.more30secCount, 1)
+ } else if session.cxxDuration > 10000 {
+ atomic.AddInt64(&cxxLauncher.more10secCount, 1)
}
+
+ <-cxxLauncher.serverCxxThrottle
+ session.PushToClientReadyChannel()
}
-func (cxxLauncher *CxxLauncher) launchServerCxxForCpp(session *Session, noccServer *NoccServer) {
- if _, err := os.Stat(session.cxxCwd); os.IsNotExist(err) {
- // {clientWorkingDir}/{clientCwd} may not exist if it doesn't contain source files (they weren't uploaded)
- // it's okay, because session.cppInFile may look like "../outer.cpp" or "/usr/local/some.cpp"
- _ = os.MkdirAll(session.cxxCwd, os.ModePerm)
- }
+func (cxxLauncher *CxxLauncher) GetNowCompilingSessionsCount() int64 {
+ return atomic.LoadInt64(&cxxLauncher.nSessionsNowCompiling)
+}
+func (cxxLauncher *CxxLauncher) GetWaitingInQueueSessionsCount() int64 {
+ return atomic.LoadInt64(&cxxLauncher.nSessionsReadyButWaiting)
+}
+
+func (cxxLauncher *CxxLauncher) GetTotalCxxCallsCount() int64 {
+ return atomic.LoadInt64(&cxxLauncher.totalCalls)
+}
+
+func (cxxLauncher *CxxLauncher) GetTotalCxxDurationMilliseconds() int64 {
+ return atomic.LoadInt64(&cxxLauncher.totalDurationMs)
+}
+
+func (cxxLauncher *CxxLauncher) GetMore10secCount() int64 {
+ return atomic.LoadInt64(&cxxLauncher.more10secCount)
+}
+
+func (cxxLauncher *CxxLauncher) GetMore30secCount() int64 {
+ return atomic.LoadInt64(&cxxLauncher.more30secCount)
+}
+
+func (cxxLauncher *CxxLauncher) GetNonZeroExitCodeCount() int64 {
+ return atomic.LoadInt64(&cxxLauncher.nonZeroExitCodeCount)
+}
+
+func (cxxLauncher *CxxLauncher) launchServerCxxForCpp(session *Session, noccServer *NoccServer) {
cxxCommand := exec.Command(session.cxxName, session.cxxCmdLine...)
cxxCommand.Dir = session.cxxCwd
var cxxStdout, cxxStderr bytes.Buffer
cxxCommand.Stderr = &cxxStderr
cxxCommand.Stdout = &cxxStdout
- logServer.Info(1, "launch cxx", "sessionID", session.sessionID, "clientID", session.client.clientID)
- atomic.AddInt64(&noccServer.Stats.cxxCalls, 1)
start := time.Now()
err := cxxCommand.Run()
+
session.cxxDuration = int32(time.Since(start).Milliseconds())
session.cxxExitCode = int32(cxxCommand.ProcessState.ExitCode())
session.cxxStdout = cxxStdout.Bytes()
@@ -53,23 +111,22 @@ func (cxxLauncher *CxxLauncher) launchServerCxxForCpp(session *Session, noccServ
}
if session.cxxExitCode != 0 {
- atomic.AddInt64(&noccServer.Stats.cxxNonZeroExitCode, 1)
logServer.Error("the C++ compiler exited with code", session.cxxExitCode, "sessionID", session.sessionID, session.cppInFile, "\ncxxCwd:", session.cxxCwd, "\ncxxCmdLine:", session.cxxName, session.cxxCmdLine, "\ncxxStdout:", strings.TrimSpace(string(session.cxxStdout)), "\ncxxStderr:", strings.TrimSpace(string(session.cxxStderr)))
+ } else if session.cxxDuration > 30000 {
+ logServer.Info(0, "compiled very heavy file", "sessionID", session.sessionID, "cxxDuration", session.cxxDuration, session.cppInFile)
}
// save to obj cache (to be safe, only if cxx output is empty)
if !session.objCacheKey.IsEmpty() {
if session.cxxExitCode == 0 && len(session.cxxStdout) == 0 && len(session.cxxStderr) == 0 {
if stat, err := os.Stat(session.objOutFile); err == nil {
- _ = noccServer.ObjFileCache.SaveFileToCache(session.objOutFile, session.objCacheKey, stat.Size())
+ _ = noccServer.ObjFileCache.SaveFileToCache(session.objOutFile, path.Base(session.cppInFile)+".o", session.objCacheKey, stat.Size())
}
}
}
- atomic.AddInt64(&noccServer.Stats.cxxTotalDurationMs, int64(session.cxxDuration))
session.cxxStdout = cxxLauncher.patchStdoutDropServerPaths(session.client, session.cxxStdout)
session.cxxStderr = cxxLauncher.patchStdoutDropServerPaths(session.client, session.cxxStderr)
- session.PushToClientReadyChannel()
}
func (cxxLauncher *CxxLauncher) launchServerCxxForPch(cxxName string, cxxCmdLine []string, rootDir string, noccServer *NoccServer) error {
@@ -94,7 +151,7 @@ func (cxxLauncher *CxxLauncher) launchServerCxxForPch(cxxName string, cxxCmdLine
return nil
}
-// patchStdoutDropServerPaths replaces /tmp/nocc-server/clients/clientID/path/to/file.cpp with /path/to/file.cpp.
+// patchStdoutDropServerPaths replaces /tmp/nocc/cpp/clients/clientID/path/to/file.cpp with /path/to/file.cpp.
// It's very handy to send back stdout/stderr without server paths.
func (cxxLauncher *CxxLauncher) patchStdoutDropServerPaths(client *Client, stdout []byte) []byte {
if len(stdout) == 0 {
diff --git a/internal/server/file-cache.go b/internal/server/file-cache.go
index d1941ff..f03c593 100644
--- a/internal/server/file-cache.go
+++ b/internal/server/file-cache.go
@@ -42,9 +42,6 @@ type FileCache struct {
const shardsDirCount = 256
func createSubdirsForFileCache(cacheDir string) error {
- if err := os.MkdirAll(cacheDir, os.ModePerm); err != nil {
- return err
- }
for i := 0; i < shardsDirCount; i++ {
dir := path.Join(cacheDir, fmt.Sprintf("%X", i))
if err := os.Mkdir(dir, os.ModePerm); err != nil {
@@ -67,14 +64,7 @@ func MakeFileCache(cacheDir string, limitBytes int64) (*FileCache, error) {
}, nil
}
-func (cache *FileCache) ExistsInCache(key common.SHA256) bool {
- cache.mu.RLock()
- _, exists := cache.table[key]
- cache.mu.RUnlock()
- return exists
-}
-
-func (cache *FileCache) CreateHardLinkFromCache(destPath string, key common.SHA256) bool {
+func (cache *FileCache) LookupInCache(key common.SHA256) string {
cache.mu.Lock()
cachedFile := cache.table[key]
if cachedFile.lruNode != nil && cachedFile.lruNode != cache.lruHead {
@@ -95,30 +85,30 @@ func (cache *FileCache) CreateHardLinkFromCache(destPath string, key common.SHA2
}
cache.mu.Unlock()
- if cachedFile.lruNode == nil {
- return false
- }
+ return cachedFile.pathInCache // empty if cachedFile doesn't exist
+}
- err := os.MkdirAll(path.Dir(destPath), os.ModePerm)
- if err != nil {
+func (cache *FileCache) CreateHardLinkFromCache(serverFileName string, key common.SHA256) bool {
+ pathInCache := cache.LookupInCache(key)
+ if len(pathInCache) == 0 {
return false
}
- err = os.Link(cachedFile.pathInCache, destPath)
+
+ // path.Dir(serverFileName) must be created in advance
+ err := os.Link(pathInCache, serverFileName)
return err == nil || os.IsExist(err)
}
-func (cache *FileCache) SaveFileToCache(srcPath string, key common.SHA256, fileSize int64) error {
+func (cache *FileCache) SaveFileToCache(srcPath string, fileNameInCacheDir string, key common.SHA256, fileSize int64) error {
uniqueID := atomic.AddInt64(&cache.lastIndex, 1)
- fileName := path.Base(srcPath)
- cachedFileName := fmt.Sprintf("%X/%s.%X", uniqueID%shardsDirCount, fileName, uniqueID)
- cachedFilePath := path.Join(cache.cacheDir, cachedFileName)
+ pathInCache := fmt.Sprintf("%s/%X/%s.%X", cache.cacheDir, uniqueID%shardsDirCount, fileNameInCacheDir, uniqueID)
- if err := os.Link(srcPath, cachedFilePath); err != nil {
+ if err := os.Link(srcPath, pathInCache); err != nil {
return err
}
newHead := &lruNode{key: key}
- value := cachedFile{pathInCache: cachedFilePath, fileSize: fileSize, lruNode: newHead}
+ value := cachedFile{pathInCache, fileSize, newHead}
cache.mu.Lock()
_, exists := cache.table[key]
if !exists {
@@ -136,7 +126,7 @@ func (cache *FileCache) SaveFileToCache(srcPath string, key common.SHA256, fileS
cache.mu.Unlock()
if exists {
- _ = os.Remove(cachedFilePath)
+ _ = os.Remove(pathInCache)
}
cache.purgeLastElementsTillLimit(cache.hardLimit)
diff --git a/internal/server/files-stream-server.go b/internal/server/files-stream-server.go
index 1d220d7..cb8cf82 100644
--- a/internal/server/files-stream-server.go
+++ b/internal/server/files-stream-server.go
@@ -5,23 +5,18 @@ import (
"io"
"os"
- "github.com/VKCOM/nocc/internal/common"
"github.com/VKCOM/nocc/pb"
)
// receiveUploadedFileByChunks is an actual implementation of piping a client stream to a local server file.
// See client.uploadFileByChunks.
-func receiveUploadedFileByChunks(stream pb.CompilationService_UploadFileStreamServer, firstChunk *pb.UploadFileChunkRequest, expectedBytes int, serverFileName string) (err error) {
+func receiveUploadedFileByChunks(noccServer *NoccServer, stream pb.CompilationService_UploadFileStreamServer, firstChunk *pb.UploadFileChunkRequest, expectedBytes int, serverFileName string) (err error) {
receivedBytes := len(firstChunk.ChunkBody)
- if receivedBytes >= expectedBytes {
- if err = common.MkdirForFile(serverFileName); err == nil {
- err = os.WriteFile(serverFileName, firstChunk.ChunkBody, os.ModePerm)
- }
- return
- }
-
- fileTmp, err := common.OpenTempFile(serverFileName, true)
+ // we write to a tmp file and rename it to serverFileName after saving
+ // it prevents races from concurrent writing to the same file
+ // (this situation is possible on a slow network when a file was requested several times)
+ fileTmp, err := noccServer.SrcFileCache.MakeTempFileForUploadSaving(serverFileName)
if err == nil {
_, err = fileTmp.Write(firstChunk.ChunkBody)
}
diff --git a/internal/server/nocc-server.go b/internal/server/nocc-server.go
index 7725f55..c59d5a2 100644
--- a/internal/server/nocc-server.go
+++ b/internal/server/nocc-server.go
@@ -7,6 +7,7 @@ import (
"net"
"os"
"os/exec"
+ "path"
"runtime"
"strconv"
"strings"
@@ -57,7 +58,6 @@ func (s *NoccServer) StartGRPCListening(listenAddr string) (net.Listener, error)
return nil, err
}
- go s.CxxLauncher.EnterInfiniteLoopToCompile(s)
go s.Cron.StartCron()
logServer.Info(0, "nocc-server started")
@@ -90,7 +90,12 @@ func (s *NoccServer) StartClient(_ context.Context, in *pb.StartClientRequest) (
return nil, err
}
- logServer.Info(0, "new client", "clientID", client.clientID, "user", in.HostUserName, "version", in.ClientVersion)
+ logServer.Info(0, "new client", "clientID", client.clientID, "version", in.ClientVersion, "; nClients", s.ActiveClients.ActiveCount())
+
+ if in.AllRemotesDelim != "" && s.ActiveClients.IsRemotesListSeenTheFirstTime(in.AllRemotesDelim, client.clientID) {
+ logServer.Info(0, "new remotes list", strings.Count(in.AllRemotesDelim, ",")+1, "clientID", client.clientID, in.AllRemotesDelim)
+ }
+
return &pb.StartClientReply{}, nil
}
@@ -106,7 +111,7 @@ func (s *NoccServer) StartCompilationSession(_ context.Context, in *pb.StartComp
return nil, status.Errorf(codes.Unauthenticated, "clientID %s not found; probably, the server was restarted just now", in.ClientID)
}
- session, err := client.StartNewSession(in)
+ session, err := client.CreateNewSession(in)
if err != nil {
atomic.AddInt64(&s.Stats.sessionsFailedOpen, 1)
logServer.Error("failed to open session", "clientID", in.ClientID, "sessionID", in.SessionID, err)
@@ -119,20 +124,30 @@ func (s *NoccServer) StartCompilationSession(_ context.Context, in *pb.StartComp
// respond that we are waiting 0 files, and the client would immediately request for a compiled obj
// it's mostly a moment of optimization: avoid calling os.Link from src cache to working dir
if !client.disableObjCache {
- session.objCacheKey = s.ObjFileCache.MakeObjCacheKey(session)
- session.objCacheExists = s.ObjFileCache.ExistsInCache(session.objCacheKey) // avoid calling ExistsInCache in the future
- if session.objCacheExists {
- logServer.Info(0, "started", "sessionID", session.sessionID, "clientID", client.clientID, "from obj cache", client.MapServerAbsToClientFileName(session.cppInFile))
+ session.objCacheKey = s.ObjFileCache.MakeObjCacheKey(in.CxxName, in.CxxArgs, session.files, in.CppInFile)
+ if pathInObjCache := s.ObjFileCache.LookupInCache(session.objCacheKey); len(pathInObjCache) != 0 {
+ session.objCacheExists = true
+ session.objOutFile = pathInObjCache // stream back this file directly
+ session.compilationStarted = 1 // client.GetSessionsNotStartedCompilation() will not return it
+
+ logServer.Info(0, "started", "sessionID", session.sessionID, "clientID", client.clientID, "from obj cache", in.CppInFile)
+ client.RegisterCreatedSession(session)
atomic.AddInt64(&s.Stats.sessionsFromObjCache, 1)
- session.StartCompilingObjIfPossible(s) // would create a hard link from obj cache instead of launching cxx
+ session.PushToClientReadyChannel()
+
return &pb.StartCompilationSessionReply{}, nil
}
}
// otherwise, we detect files that don't exist in src cache and request a client to upload them
-
- // here we deal with concurrency: multiple nocc clients connect to this nocc server
- // they simultaneously create sessions and want to upload files, maybe equal files
- // our goal is to let the first client upload the file X, others will just wait if they also depend on X
+ // before restoring from src cache, ensure that all client dirs structure is mirrored to workingDir
+ session.PrepareServerCxxCmdLine(s, in.Cwd, in.CxxArgs, in.CxxIDirs)
+ client.MkdirAllForSession(session)
+
+ // here we deal with concurrency:
+ // one nocc client creates multiple sessions that depend on equal h files
+ // our goal is to let the client upload file X only once:
+ // the first session is responded "need X to be uploaded", whereas other sessions just wait
+ // note, that if X is in src-cache, it's just hard linked from there to serverFileName
fileIndexesToUpload := make([]uint32, 0, len(session.files))
for index, file := range session.files {
switch file.state {
@@ -145,7 +160,7 @@ func (s *NoccServer) StartCompilationSession(_ context.Context, in *pb.StartComp
return nil, fmt.Errorf("system file %s differs between a client and a server", file.serverFileName)
}
if isSystemFile {
- logServer.Info(2, "file", file.serverFileName, "is in src-cache, no need to upload")
+ logServer.Info(2, "file", file.serverFileName, "is a system file, no need to upload")
file.state = fsFileStateUploaded
continue
}
@@ -163,7 +178,7 @@ func (s *NoccServer) StartCompilationSession(_ context.Context, in *pb.StartComp
fileIndexesToUpload = append(fileIndexesToUpload, uint32(index))
case fsFileStateUploading:
- if !client.IsFileUploadFailed(file) { // another client is uploading this file currently
+ if !client.IsFileUploadHanged(file) { // this file is already requested to be uploaded
continue
}
@@ -183,9 +198,11 @@ func (s *NoccServer) StartCompilationSession(_ context.Context, in *pb.StartComp
case fsFileStateUploaded:
}
}
- logServer.Info(0, "started", "sessionID", session.sessionID, "clientID", client.clientID, "waiting", len(fileIndexesToUpload), "uploads", client.MapClientFileNameToServerAbs(session.cppInFile))
+ logServer.Info(0, "started", "sessionID", session.sessionID, "clientID", client.clientID, "waiting", len(fileIndexesToUpload), "uploads", in.CppInFile)
+ client.RegisterCreatedSession(session)
launchCxxOnServerOnReadySessions(s, client) // other sessions could also be waiting for files in src-cache
+
return &pb.StartCompilationSessionReply{
FileIndexesToUpload: fileIndexesToUpload,
}, nil
@@ -227,7 +244,7 @@ func (s *NoccServer) UploadFileStream(stream pb.CompilationService_UploadFileStr
logServer.Info(0, "start receiving large file", file.fileSize, "sessionID", session.sessionID, clientFileName)
}
- if err := receiveUploadedFileByChunks(stream, firstChunk, int(file.fileSize), file.serverFileName); err != nil {
+ if err := receiveUploadedFileByChunks(s, stream, firstChunk, int(file.fileSize), file.serverFileName); err != nil {
file.state = fsFileStateUploadError
logServer.Error("fs uploading->error", "sessionID", session.sessionID, clientFileName, err)
return fmt.Errorf("can't receive file %q: %v", clientFileName, err)
@@ -252,7 +269,7 @@ func (s *NoccServer) UploadFileStream(stream pb.CompilationService_UploadFileStr
logServer.Info(1, "fs uploading->uploaded", "sessionID", session.sessionID, clientFileName)
launchCxxOnServerOnReadySessions(s, session.client) // other sessions could also be waiting for this file, we should check all
_ = stream.Send(&pb.UploadFileReply{})
- _ = s.SrcFileCache.SaveFileToCache(file.serverFileName, file.fileSHA256, file.fileSize)
+ _ = s.SrcFileCache.SaveFileToCache(file.serverFileName, path.Base(file.serverFileName), file.fileSHA256, file.fileSize)
atomic.AddInt64(&s.Stats.bytesReceived, file.fileSize)
atomic.AddInt64(&s.Stats.filesReceived, 1)
@@ -307,7 +324,7 @@ func (s *NoccServer) RecvCompiledObjStream(in *pb.OpenReceiveStreamRequest, stre
return onError(session.sessionID, "can't send obj non-0 reply sessionID %d clientID %s %v", session.sessionID, client.clientID, err)
}
} else {
- logServer.Info(2, "sending obj file", session.objOutFile, "sessionID", session.sessionID)
+ logServer.Info(0, "send obj file", "sessionID", session.sessionID, "clientID", client.clientID, "cxxDuration", session.cxxDuration, session.objOutFile)
bytesSent, err := sendObjFileByChunks(stream, chunkBuf, session)
if err != nil {
return onError(session.sessionID, "can't send obj file %s sessionID %d clientID %s %v", session.objOutFile, session.sessionID, client.clientID, err)
@@ -327,7 +344,7 @@ func (s *NoccServer) RecvCompiledObjStream(in *pb.OpenReceiveStreamRequest, stre
func (s *NoccServer) StopClient(_ context.Context, in *pb.StopClientRequest) (*pb.StopClientReply, error) {
client := s.ActiveClients.GetClient(in.ClientID)
if client != nil {
- logServer.Info(0, "client disconnected", "clientID", client.clientID)
+ logServer.Info(0, "client disconnected", "clientID", client.clientID, "; nClients", s.ActiveClients.ActiveCount()-1)
// removing working dir could take some time, but respond immediately
go s.ActiveClients.DeleteClient(client)
}
@@ -352,16 +369,28 @@ func (s *NoccServer) Status(context.Context, *pb.StatusRequest) (*pb.StatusReply
gccRawOut, _ := exec.Command("g++", "-v").CombinedOutput()
clangRawOut, _ := exec.Command("clang", "-v").CombinedOutput()
+ uNameRV, _ := exec.Command("uname", "-rv").CombinedOutput()
+
+ var rLimit syscall.Rlimit
+ _ = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
return &pb.StatusReply{
- ServerVersion: common.GetVersion(),
- ServerArgs: os.Args,
- ServerUptime: int64(time.Since(s.StartTime)),
- GccVersion: detectVersionFromConsoleOutput(gccRawOut),
- ClangVersion: detectVersionFromConsoleOutput(clangRawOut),
- LogFileSize: logServer.GetFileSize(),
- SrcCacheSize: s.SrcFileCache.GetBytesOnDisk(),
- ObjCacheSize: s.ObjFileCache.GetBytesOnDisk(),
+ ServerVersion: common.GetVersion(),
+ ServerArgs: os.Args,
+ ServerUptime: int64(time.Since(s.StartTime)),
+ GccVersion: detectVersionFromConsoleOutput(gccRawOut),
+ ClangVersion: detectVersionFromConsoleOutput(clangRawOut),
+ LogFileSize: logServer.GetFileSize(),
+ SrcCacheSize: s.SrcFileCache.GetBytesOnDisk(),
+ ObjCacheSize: s.ObjFileCache.GetBytesOnDisk(),
+ ULimit: int64(rLimit.Cur),
+ UName: strings.TrimSpace(string(uNameRV)),
+ SessionsTotal: atomic.LoadInt64(&s.Stats.sessionsCount),
+ SessionsActive: s.ActiveClients.ActiveSessionsCount(),
+ CxxCalls: s.CxxLauncher.GetTotalCxxCallsCount(),
+ CxxDurMore10Sec: s.CxxLauncher.GetMore10secCount(),
+ CxxDurMore30Sec: s.CxxLauncher.GetMore30secCount(),
+ UniqueRemotes: s.ActiveClients.GetUniqueRemotesListInfo(),
}, nil
}
diff --git a/internal/server/obj-cache.go b/internal/server/obj-cache.go
index c3a328c..1fa95f1 100644
--- a/internal/server/obj-cache.go
+++ b/internal/server/obj-cache.go
@@ -2,14 +2,14 @@ package server
import (
"crypto/sha256"
+ "fmt"
"path"
- "strconv"
"strings"
"github.com/VKCOM/nocc/internal/common"
)
-// ObjFileCache is a /tmp/nocc-server/obj-cache directory, where the resulting .o files are saved.
+// ObjFileCache is a /tmp/nocc/obj/obj-cache directory, where the resulting .o files are saved.
// Its purpose is to reuse a ready .o file if the same .cpp is requested to be compiled again.
// This is especially useful to share .o files across build agents:
// if one build agent compiles the master branch, other build agents can reuse ready .o for every .cpp.
@@ -17,15 +17,19 @@ import (
// See ObjFileCache.MakeObjCacheKey.
type ObjFileCache struct {
*FileCache
+
+ // next to obj-cache, there is a /tmp/nocc/obj/cxx-out directory (session.objOutFile point here)
+ // after being compiled, files from here are hard linked to obj-cache
+ objTmpDir string
}
-func MakeObjFileCache(cacheDir string, limitBytes int64) (*ObjFileCache, error) {
+func MakeObjFileCache(cacheDir string, objTmpDir string, limitBytes int64) (*ObjFileCache, error) {
cache, err := MakeFileCache(cacheDir, limitBytes)
if err != nil {
return nil, err
}
- return &ObjFileCache{cache}, nil
+ return &ObjFileCache{cache, strings.TrimSuffix(objTmpDir, "/")}, nil
}
// MakeObjCacheKey creates a unique key (sha256) for an input .cpp file and all its dependencies.
@@ -40,40 +44,31 @@ func MakeObjFileCache(cacheDir string, limitBytes int64) (*ObjFileCache, error)
// * all C++ compiler options are the same
//
// The problem is with the last point. cxxCmdLine contains -I and other options that vary between clients:
-// > -iquote /tmp/nocc-server/clients/{clientID}/home/{username}/proj -I /tmp/gch/{random_hash} -o ...{random_int}.o
+// > -iquote /tmp/nocc/cpp/clients/{clientID}/home/{username}/proj -I /tmp/gch/{random_hash} -o ...{random_int}.o
// These are different options, but in fact, they should be considered the same.
// That's why we don't take include paths into account when calculating a hash from cxxCmdLine.
// The assumption is: if all deps are equal, their actual paths/names don't matter.
-func (cache *ObjFileCache) MakeObjCacheKey(session *Session) common.SHA256 {
- depsStr := strings.Builder{}
- depsStr.Grow(4096)
-
- depsStr.WriteString(session.cxxName)
- depsStr.WriteString("; args = ")
+func (cache *ObjFileCache) MakeObjCacheKey(cxxName string, cxxArgs []string, sessionFiles []*fileInClientDir, cppInFile string) common.SHA256 {
+ hasher := sha256.New()
- for i := 0; i < len(session.cxxCmdLine)-1; i++ { // -1, as the last arg is an input file
- arg := session.cxxCmdLine[i]
- if arg == "-I" || arg == "-iquote" || arg == "-isystem" || arg == "-include" || arg == "-o" {
- i++
- } else {
- depsStr.WriteString(arg)
- depsStr.WriteString(" ")
- }
+ hasher.Write([]byte(cxxName))
+ for _, arg := range cxxArgs {
+ hasher.Write([]byte(arg))
}
-
- depsStr.WriteString("; deps ")
- depsStr.WriteString(strconv.Itoa(len(session.files)))
- depsStr.WriteString("; in ")
- depsStr.WriteString(path.Base(session.cppInFile)) // just a protection; not a full path, as it varies between clients
-
- hasher := sha256.New()
- hasher.Write([]byte(depsStr.String()))
+ hasher.Write([]byte(path.Base(cppInFile))) // not a full path, as it varies between clients
sha256xor := common.MakeSHA256Struct(hasher)
- for _, file := range session.files {
+ sha256xor.B8_15 ^= uint64(len(cxxArgs))
+ sha256xor.B16_23 ^= uint64(len(sessionFiles))
+ for _, file := range sessionFiles {
sha256xor.XorWith(&file.fileSHA256)
sha256xor.B0_7 ^= uint64(file.fileSize)
}
return sha256xor
}
+
+// GenerateObjOutFileName generates session.objOutFile (destination for C++ compiler launched on a server)
+func (cache *ObjFileCache) GenerateObjOutFileName(session *Session) string {
+ return fmt.Sprintf("%s/%s.%d.o", cache.objTmpDir, session.client.clientID, session.sessionID)
+}
diff --git a/internal/server/pch-compilation.go b/internal/server/pch-compilation.go
index d40f47f..7390ad2 100644
--- a/internal/server/pch-compilation.go
+++ b/internal/server/pch-compilation.go
@@ -27,10 +27,6 @@ type PchCompilation struct {
}
func MakePchCompilation(allPchDir string) (*PchCompilation, error) {
- if err := os.MkdirAll(allPchDir, os.ModePerm); err != nil {
- return nil, err
- }
-
return &PchCompilation{
allPchDir: allPchDir,
compiledPchList: make(map[common.SHA256]*compiledPchItem, 10),
diff --git a/internal/server/session.go b/internal/server/session.go
index e5fbe2b..47e4f33 100644
--- a/internal/server/session.go
+++ b/internal/server/session.go
@@ -18,10 +18,10 @@ import (
type Session struct {
sessionID uint32
- cppInFile string
- objOutFile string
- cxxCwd string
- cxxName string
+ cppInFile string // as-is from a client cmd line (relative to cxxCwd on a server-side)
+ objOutFile string // inside /tmp/nocc/obj/cxx-out, or directly in /tmp/nocc/obj/obj-cache if taken from cache
+ cxxCwd string // cwd for the C++ compiler on a server-side (= client.workingDir + clientCwd)
+ cxxName string // g++ / clang / etc.
cxxCmdLine []string
client *Client
@@ -40,7 +40,29 @@ type Session struct {
// PrepareServerCxxCmdLine prepares a command line for cxx invocation.
// Notably, options like -Wall and -fpch-preprocess are pushed as is,
// but include dirs like /home/alice/headers need to be remapped to point to server dir.
-func (session *Session) PrepareServerCxxCmdLine(cxxArgs []string, cxxIDirs []string) []string {
+func (session *Session) PrepareServerCxxCmdLine(noccServer *NoccServer, clientCwd string, cxxArgs []string, cxxIDirs []string) {
+ session.objOutFile = noccServer.ObjFileCache.GenerateObjOutFileName(session)
+
+ var cppInFile string
+ // old clients that don't send this field (they send abs cppInFile)
+ // todo delete later, after upgrading all clients
+ if clientCwd == "" {
+ cppInFile = session.client.MapClientFileNameToServerAbs(session.cppInFile)
+ session.cxxCwd = session.client.workingDir
+ } else {
+ // session.cppInFile is as-is from a client cmd line:
+ // * "/abs/path" becomes "client.workingDir/abs/path"
+ // (except for system files, /usr/include left unchanged)
+ // * "rel/path" (relative to clientCwd) is left as-is (becomes relative to session.cxxCwd)
+ // (for correct __FILE__ expansion and other minor specifics)
+ if session.cppInFile[0] == '/' {
+ cppInFile = session.client.MapClientFileNameToServerAbs(session.cppInFile)
+ } else {
+ cppInFile = session.cppInFile
+ }
+ session.cxxCwd = session.client.MapClientFileNameToServerAbs(clientCwd)
+ }
+
cxxCmdLine := make([]string, 0, len(cxxIDirs)+len(cxxArgs)+3)
// loop through -I {dir} / -include {file} / etc. (format is guaranteed), converting client {dir} to server path
@@ -51,25 +73,14 @@ func (session *Session) PrepareServerCxxCmdLine(cxxArgs []string, cxxIDirs []str
}
// append -Wall and other cxx args
cxxCmdLine = append(cxxCmdLine, cxxArgs...)
- // append output and input (they won't take part in obj cache key calculation, like -I)
- return append(cxxCmdLine, "-o", session.objOutFile, session.cppInFile)
+ // build final string
+ session.cxxCmdLine = append(cxxCmdLine, "-o", session.objOutFile, cppInFile)
}
// StartCompilingObjIfPossible executes cxx if all dependent files (.cpp/.h/.nocc-pch/etc.) are ready.
// They have either been uploaded by the client or already taken from src cache.
+// Note, that it's called for sessions that don't exist in obj cache.
func (session *Session) StartCompilingObjIfPossible(noccServer *NoccServer) {
- // optimistic path: if .o file exists in cache, files aren't needed to (and aren't requested to) be uploaded
- if session.objCacheExists { // avoid calling ExistsInCache (when false, it's launched on every file upload)
- if atomic.SwapInt32(&session.compilationStarted, 1) == 0 {
- logServer.Info(2, "get obj from cache", "sessionID", session.sessionID, session.objOutFile)
- if !noccServer.ObjFileCache.CreateHardLinkFromCache(session.objOutFile, session.objCacheKey) {
- logServer.Error("could not create hard link from obj cache", "sessionID", session.sessionID)
- }
- session.PushToClientReadyChannel()
- }
- return
- }
-
for _, file := range session.files {
if file.state != fsFileStateUploaded {
return
@@ -77,7 +88,7 @@ func (session *Session) StartCompilingObjIfPossible(noccServer *NoccServer) {
}
if atomic.SwapInt32(&session.compilationStarted, 1) == 0 {
- noccServer.CxxLauncher.chanToCompile <- session
+ go noccServer.CxxLauncher.LaunchCxxWhenPossible(noccServer, session)
}
}
@@ -86,5 +97,6 @@ func (session *Session) PushToClientReadyChannel() {
select {
case <-session.client.chanDisconnected:
case session.client.chanReadySessions <- session:
+ // note, that if this chan is full, this 'case' (and this function call) is blocking
}
}
diff --git a/internal/server/src-cache.go b/internal/server/src-cache.go
index 32ebdbd..ca39f02 100644
--- a/internal/server/src-cache.go
+++ b/internal/server/src-cache.go
@@ -1,6 +1,12 @@
package server
-// SrcFileCache is a /tmp/nocc-server/src-cache directory, where uploaded .cpp/.h/etc. files are saved.
+import (
+ "math/rand"
+ "os"
+ "strconv"
+)
+
+// SrcFileCache is a /tmp/nocc/cpp/src-cache directory, where uploaded .cpp/.h/etc. files are saved.
// It's supposed that sha256 uniquely identifies the file, that's why a map key doesn't contain size/mtime.
// It's useful to share files across clients (if one client has uploaded a file, the second takes it from cache).
// Also, it helps reuse files across the same client after it was considered inactive and deleted, but launched again.
@@ -16,3 +22,9 @@ func MakeSrcFileCache(cacheDir string, limitBytes int64) (*SrcFileCache, error)
return &SrcFileCache{cache}, nil
}
+
+func (cache *SrcFileCache) MakeTempFileForUploadSaving(serverFileName string) (*os.File, error) {
+ // path.Dir(serverFileName) is created in advance, see Client.MkdirAllForSession()
+ fileNameTmp := serverFileName + "." + strconv.Itoa(rand.Int())
+ return os.OpenFile(fileNameTmp, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.ModePerm)
+}
diff --git a/internal/server/statsd.go b/internal/server/statsd.go
index 916597e..ddec06f 100644
--- a/internal/server/statsd.go
+++ b/internal/server/statsd.go
@@ -23,9 +23,6 @@ type Statsd struct {
sessionsCount int64
sessionsFailedOpen int64
sessionsFromObjCache int64
- cxxCalls int64
- cxxTotalDurationMs int64
- cxxNonZeroExitCode int64
pchCompilations int64
pchCompilationsFailed int64
@@ -68,9 +65,13 @@ func (cs *Statsd) fillBufferWithStats(noccServer *NoccServer) {
cs.writeStat("clients.files_count", noccServer.ActiveClients.TotalFilesCountInDirs())
cs.writeStat("clients.unauthenticated", atomic.LoadInt64(&cs.clientsUnauthenticated))
- cs.writeStat("cxx.calls", atomic.LoadInt64(&cs.cxxCalls))
- cs.writeStat("cxx.duration", atomic.LoadInt64(&cs.cxxTotalDurationMs))
- cs.writeStat("cxx.nonzero", atomic.LoadInt64(&cs.cxxNonZeroExitCode))
+ cs.writeStat("cxx.calls", noccServer.CxxLauncher.GetTotalCxxCallsCount())
+ cs.writeStat("cxx.parallel", noccServer.CxxLauncher.GetNowCompilingSessionsCount())
+ cs.writeStat("cxx.waiting", noccServer.CxxLauncher.GetWaitingInQueueSessionsCount())
+ cs.writeStat("cxx.duration", noccServer.CxxLauncher.GetTotalCxxDurationMilliseconds())
+ cs.writeStat("cxx.more10sec", noccServer.CxxLauncher.GetMore10secCount())
+ cs.writeStat("cxx.more30sec", noccServer.CxxLauncher.GetMore30secCount())
+ cs.writeStat("cxx.nonzero", noccServer.CxxLauncher.GetNonZeroExitCodeCount())
cs.writeStat("pch.calls", atomic.LoadInt64(&cs.pchCompilations))
cs.writeStat("pch.failed", atomic.LoadInt64(&cs.pchCompilationsFailed))
@@ -107,7 +108,10 @@ func (cs *Statsd) SendToStatsd(noccServer *NoccServer) {
cs.fillBufferWithStats(noccServer)
- _, _ = io.Copy(cs.statsdConnection, &cs.statsdBuffer)
+ _, err := io.Copy(cs.statsdConnection, &cs.statsdBuffer)
+ if err != nil {
+ logServer.Error("writing to statsd", err)
+ }
cs.statsdBuffer.Reset()
}
diff --git a/internal/server/system-headers.go b/internal/server/system-headers.go
index cf019e8..58d9e6a 100644
--- a/internal/server/system-headers.go
+++ b/internal/server/system-headers.go
@@ -17,7 +17,7 @@ type systemHeader struct {
// SystemHeadersCache stores info about system headers (typically, inside /usr/include).
// If a client wants to send /usr/include/math.h, and it's the same as here on the server,
// a client doesn't have to send its body,
-// because we'll use a server's one instead of saving it to /tmp/nocc-server/client/{clientID}/usr/include/math.h.
+// because we'll use a server's one instead of saving it to /tmp/nocc/cpp/client/{clientID}/usr/include/math.h.
// It's supposed, that system headers are in default include path of cxx on the server.
// Without system headers detection, everything still works, it's just a moment of optimization.
type SystemHeadersCache struct {
diff --git a/pb/nocc-protobuf.pb.go b/pb/nocc-protobuf.pb.go
index f15cc36..a62e9ff 100644
--- a/pb/nocc-protobuf.pb.go
+++ b/pb/nocc-protobuf.pb.go
@@ -116,6 +116,7 @@ type StartClientRequest struct {
HostUserName string `protobuf:"bytes,2,opt,name=HostUserName,proto3" json:"HostUserName,omitempty"`
ClientVersion string `protobuf:"bytes,3,opt,name=ClientVersion,proto3" json:"ClientVersion,omitempty"`
DisableObjCache bool `protobuf:"varint,10,opt,name=DisableObjCache,proto3" json:"DisableObjCache,omitempty"`
+ AllRemotesDelim string `protobuf:"bytes,20,opt,name=AllRemotesDelim,proto3" json:"AllRemotesDelim,omitempty"`
}
func (x *StartClientRequest) Reset() {
@@ -178,6 +179,13 @@ func (x *StartClientRequest) GetDisableObjCache() bool {
return false
}
+func (x *StartClientRequest) GetAllRemotesDelim() string {
+ if x != nil {
+ return x.AllRemotesDelim
+ }
+ return ""
+}
+
type StartClientReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -745,14 +753,22 @@ type StatusReply struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- ServerVersion string `protobuf:"bytes,1,opt,name=ServerVersion,proto3" json:"ServerVersion,omitempty"`
- ServerArgs []string `protobuf:"bytes,2,rep,name=ServerArgs,proto3" json:"ServerArgs,omitempty"`
- ServerUptime int64 `protobuf:"varint,3,opt,name=ServerUptime,proto3" json:"ServerUptime,omitempty"`
- GccVersion string `protobuf:"bytes,4,opt,name=GccVersion,proto3" json:"GccVersion,omitempty"`
- ClangVersion string `protobuf:"bytes,5,opt,name=ClangVersion,proto3" json:"ClangVersion,omitempty"`
- LogFileSize int64 `protobuf:"varint,6,opt,name=LogFileSize,proto3" json:"LogFileSize,omitempty"`
- SrcCacheSize int64 `protobuf:"varint,7,opt,name=SrcCacheSize,proto3" json:"SrcCacheSize,omitempty"`
- ObjCacheSize int64 `protobuf:"varint,8,opt,name=ObjCacheSize,proto3" json:"ObjCacheSize,omitempty"`
+ ServerVersion string `protobuf:"bytes,1,opt,name=ServerVersion,proto3" json:"ServerVersion,omitempty"`
+ ServerArgs []string `protobuf:"bytes,2,rep,name=ServerArgs,proto3" json:"ServerArgs,omitempty"`
+ ServerUptime int64 `protobuf:"varint,3,opt,name=ServerUptime,proto3" json:"ServerUptime,omitempty"`
+ GccVersion string `protobuf:"bytes,4,opt,name=GccVersion,proto3" json:"GccVersion,omitempty"`
+ ClangVersion string `protobuf:"bytes,5,opt,name=ClangVersion,proto3" json:"ClangVersion,omitempty"`
+ LogFileSize int64 `protobuf:"varint,6,opt,name=LogFileSize,proto3" json:"LogFileSize,omitempty"`
+ SrcCacheSize int64 `protobuf:"varint,7,opt,name=SrcCacheSize,proto3" json:"SrcCacheSize,omitempty"`
+ ObjCacheSize int64 `protobuf:"varint,8,opt,name=ObjCacheSize,proto3" json:"ObjCacheSize,omitempty"`
+ ULimit int64 `protobuf:"varint,9,opt,name=ULimit,proto3" json:"ULimit,omitempty"`
+ UName string `protobuf:"bytes,10,opt,name=UName,proto3" json:"UName,omitempty"`
+ SessionsTotal int64 `protobuf:"varint,11,opt,name=SessionsTotal,proto3" json:"SessionsTotal,omitempty"`
+ SessionsActive int64 `protobuf:"varint,12,opt,name=SessionsActive,proto3" json:"SessionsActive,omitempty"`
+ CxxCalls int64 `protobuf:"varint,20,opt,name=CxxCalls,proto3" json:"CxxCalls,omitempty"`
+ CxxDurMore10Sec int64 `protobuf:"varint,21,opt,name=CxxDurMore10sec,proto3" json:"CxxDurMore10sec,omitempty"`
+ CxxDurMore30Sec int64 `protobuf:"varint,22,opt,name=CxxDurMore30sec,proto3" json:"CxxDurMore30sec,omitempty"`
+ UniqueRemotes []string `protobuf:"bytes,30,rep,name=UniqueRemotes,proto3" json:"UniqueRemotes,omitempty"`
}
func (x *StatusReply) Reset() {
@@ -843,6 +859,62 @@ func (x *StatusReply) GetObjCacheSize() int64 {
return 0
}
+func (x *StatusReply) GetULimit() int64 {
+ if x != nil {
+ return x.ULimit
+ }
+ return 0
+}
+
+func (x *StatusReply) GetUName() string {
+ if x != nil {
+ return x.UName
+ }
+ return ""
+}
+
+func (x *StatusReply) GetSessionsTotal() int64 {
+ if x != nil {
+ return x.SessionsTotal
+ }
+ return 0
+}
+
+func (x *StatusReply) GetSessionsActive() int64 {
+ if x != nil {
+ return x.SessionsActive
+ }
+ return 0
+}
+
+func (x *StatusReply) GetCxxCalls() int64 {
+ if x != nil {
+ return x.CxxCalls
+ }
+ return 0
+}
+
+func (x *StatusReply) GetCxxDurMore10Sec() int64 {
+ if x != nil {
+ return x.CxxDurMore10Sec
+ }
+ return 0
+}
+
+func (x *StatusReply) GetCxxDurMore30Sec() int64 {
+ if x != nil {
+ return x.CxxDurMore30Sec
+ }
+ return 0
+}
+
+func (x *StatusReply) GetUniqueRemotes() []string {
+ if x != nil {
+ return x.UniqueRemotes
+ }
+ return nil
+}
+
type DumpLogsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1047,7 +1119,7 @@ var file_pb_nocc_protobuf_proto_rawDesc = []byte{
0x42, 0x31, 0x36, 0x5f, 0x32, 0x33, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0b, 0x53, 0x48,
0x41, 0x32, 0x35, 0x36, 0x42, 0x31, 0x36, 0x32, 0x33, 0x12, 0x22, 0x0a, 0x0d, 0x53, 0x48, 0x41,
0x32, 0x35, 0x36, 0x5f, 0x42, 0x32, 0x34, 0x5f, 0x33, 0x31, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x06,
- 0x52, 0x0b, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x42, 0x32, 0x34, 0x33, 0x31, 0x22, 0xa4, 0x01,
+ 0x52, 0x0b, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x42, 0x32, 0x34, 0x33, 0x31, 0x22, 0xce, 0x01,
0x0a, 0x12, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44,
@@ -1058,138 +1130,158 @@ var file_pb_nocc_protobuf_proto_rawDesc = []byte{
0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x44, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x6a, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0f, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x6a, 0x43,
- 0x61, 0x63, 0x68, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69,
- 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x94, 0x02, 0x0a, 0x1e, 0x53, 0x74, 0x61,
+ 0x61, 0x63, 0x68, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x6d, 0x6f, 0x74,
+ 0x65, 0x73, 0x44, 0x65, 0x6c, 0x69, 0x6d, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x41,
+ 0x6c, 0x6c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x44, 0x65, 0x6c, 0x69, 0x6d, 0x22, 0x12,
+ 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70,
+ 0x6c, 0x79, 0x22, 0x94, 0x02, 0x0a, 0x1e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70,
+ 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
+ 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
+ 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12,
+ 0x10, 0x0a, 0x03, 0x43, 0x77, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x43, 0x77,
+ 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x70, 0x70, 0x49, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x0a,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x43, 0x70, 0x70, 0x49, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x12,
+ 0x18, 0x0a, 0x07, 0x43, 0x78, 0x78, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x07, 0x43, 0x78, 0x78, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x78, 0x78,
+ 0x41, 0x72, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x43, 0x78, 0x78, 0x41,
+ 0x72, 0x67, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x78, 0x78, 0x49, 0x44, 0x69, 0x72, 0x73, 0x18,
+ 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x43, 0x78, 0x78, 0x49, 0x44, 0x69, 0x72, 0x73, 0x12,
+ 0x38, 0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73,
+ 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x46, 0x69,
+ 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75,
+ 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x1c, 0x53, 0x74, 0x61,
0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x73,
- 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43,
+ 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x46, 0x69, 0x6c,
+ 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x54, 0x6f, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64,
+ 0x18, 0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65,
+ 0x78, 0x65, 0x73, 0x54, 0x6f, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x8e, 0x01, 0x0a, 0x16,
+ 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
+ 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
+ 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44,
+ 0x12, 0x1c, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x0d, 0x52, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1c,
+ 0x0a, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
+ 0x0c, 0x52, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x11, 0x0a, 0x0f,
+ 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
+ 0x36, 0x0a, 0x18, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x53, 0x74,
+ 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43,
- 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69,
- 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x53, 0x65, 0x73, 0x73,
- 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x43, 0x77, 0x64, 0x18, 0x03, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x03, 0x43, 0x77, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x70, 0x70, 0x49, 0x6e,
- 0x46, 0x69, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x43, 0x70, 0x70, 0x49,
- 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x78, 0x78, 0x4e, 0x61, 0x6d, 0x65,
- 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x78, 0x78, 0x4e, 0x61, 0x6d, 0x65, 0x12,
- 0x18, 0x0a, 0x07, 0x43, 0x78, 0x78, 0x41, 0x72, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09,
- 0x52, 0x07, 0x43, 0x78, 0x78, 0x41, 0x72, 0x67, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x78, 0x78,
- 0x49, 0x44, 0x69, 0x72, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x43, 0x78, 0x78,
- 0x49, 0x44, 0x69, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65,
- 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6e,
- 0x6f, 0x63, 0x63, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
- 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22,
- 0x50, 0x0a, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12,
- 0x30, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x54, 0x6f,
- 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x13, 0x46, 0x69,
- 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x54, 0x6f, 0x55, 0x70, 0x6c, 0x6f, 0x61,
- 0x64, 0x22, 0x8e, 0x01, 0x0a, 0x16, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65,
- 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08,
- 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
- 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73,
- 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x53, 0x65, 0x73,
- 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e,
- 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x49,
- 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64,
- 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f,
- 0x64, 0x79, 0x22, 0x11, 0x0a, 0x0f, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65,
- 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x36, 0x0a, 0x18, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x63,
- 0x65, 0x69, 0x76, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x22, 0xf3, 0x01,
- 0x0a, 0x19, 0x52, 0x65, 0x63, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62,
- 0x6a, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x53,
- 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09,
- 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x78, 0x78,
- 0x45, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b,
- 0x43, 0x78, 0x78, 0x45, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x43,
- 0x78, 0x78, 0x53, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09,
- 0x43, 0x78, 0x78, 0x53, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x78, 0x78,
- 0x53, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x78,
- 0x78, 0x53, 0x74, 0x64, 0x65, 0x72, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x78, 0x78, 0x44, 0x75,
- 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x43, 0x78,
- 0x78, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x69, 0x6c,
- 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x46, 0x69, 0x6c,
- 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f,
- 0x64, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42,
- 0x6f, 0x64, 0x79, 0x22, 0x2f, 0x0a, 0x11, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e,
- 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65,
- 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65,
- 0x6e, 0x74, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65,
- 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75,
- 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa5, 0x02, 0x0a, 0x0b, 0x53, 0x74, 0x61,
- 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76,
- 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x0d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e,
- 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03,
- 0x28, 0x09, 0x52, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x72, 0x67, 0x73, 0x12, 0x22,
- 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03,
- 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x70, 0x74, 0x69,
- 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x47, 0x63, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
- 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x47, 0x63, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69,
- 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x6e, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69,
- 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x61, 0x6e, 0x67, 0x56,
- 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
- 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4c, 0x6f, 0x67,
- 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x53, 0x72, 0x63, 0x43,
- 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
- 0x53, 0x72, 0x63, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0c,
- 0x4f, 0x62, 0x6a, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01,
- 0x28, 0x03, 0x52, 0x0c, 0x4f, 0x62, 0x6a, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65,
- 0x22, 0x11, 0x0a, 0x0f, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x0d, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52,
- 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x45,
- 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
- 0x65, 0x45, 0x78, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64,
- 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f,
- 0x64, 0x79, 0x22, 0x16, 0x0a, 0x14, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61, 0x63,
- 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x12, 0x44, 0x72,
- 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
- 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x53, 0x72, 0x63, 0x46, 0x69,
- 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x70,
- 0x65, 0x64, 0x53, 0x72, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x72,
- 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20,
- 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x46,
- 0x69, 0x6c, 0x65, 0x73, 0x32, 0xe4, 0x04, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x53,
- 0x74, 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x63,
- 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72,
- 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x65,
- 0x0a, 0x17, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69,
- 0x6f, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x6e, 0x6f, 0x63, 0x63,
- 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f,
- 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
- 0x22, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70,
- 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
- 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x10, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46,
- 0x69, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x6e, 0x6f, 0x63, 0x63,
- 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b,
- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x55,
- 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
- 0x28, 0x01, 0x30, 0x01, 0x12, 0x5c, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x76, 0x43, 0x6f, 0x6d, 0x70,
- 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e,
- 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
- 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
- 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
- 0x64, 0x4f, 0x62, 0x6a, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
- 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
- 0x12, 0x17, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65,
- 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6e, 0x6f, 0x63, 0x63,
- 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79,
- 0x22, 0x00, 0x12, 0x32, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x13, 0x2e, 0x6e,
- 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x1a, 0x11, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
- 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x08, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f,
- 0x67, 0x73, 0x12, 0x15, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f,
- 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6e, 0x6f, 0x63, 0x63,
- 0x2e, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
- 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0d, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61, 0x63,
- 0x68, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x41,
- 0x6c, 0x6c, 0x43, 0x61, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
- 0x18, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61,
- 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x1a, 0x5a, 0x18, 0x67,
- 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x56, 0x4b, 0x43, 0x4f, 0x4d, 0x2f,
- 0x6e, 0x6f, 0x63, 0x63, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x22, 0xf3, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x76,
+ 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x43, 0x68, 0x75, 0x6e, 0x6b,
+ 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+ 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
+ 0x6e, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x78, 0x78, 0x45, 0x78, 0x69, 0x74, 0x43, 0x6f,
+ 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x43, 0x78, 0x78, 0x45, 0x78, 0x69,
+ 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x78, 0x78, 0x53, 0x74, 0x64, 0x6f,
+ 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x78, 0x78, 0x53, 0x74, 0x64,
+ 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x78, 0x78, 0x53, 0x74, 0x64, 0x65, 0x72, 0x72,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x78, 0x78, 0x53, 0x74, 0x64, 0x65, 0x72,
+ 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x78, 0x78, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x43, 0x78, 0x78, 0x44, 0x75, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18,
+ 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12,
+ 0x1c, 0x0a, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x07, 0x20, 0x01,
+ 0x28, 0x0c, 0x52, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x2f, 0x0a,
+ 0x11, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x22, 0x11,
+ 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c,
+ 0x79, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x22, 0xb7, 0x04, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70,
+ 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73,
+ 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x65,
+ 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x41, 0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x41, 0x72, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a,
+ 0x47, 0x63, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0a, 0x47, 0x63, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c,
+ 0x43, 0x6c, 0x61, 0x6e, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x61, 0x6e, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+ 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18,
+ 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69,
+ 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x53, 0x72, 0x63, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69,
+ 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x53, 0x72, 0x63, 0x43, 0x61, 0x63,
+ 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4f, 0x62, 0x6a, 0x43, 0x61, 0x63,
+ 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x4f, 0x62,
+ 0x6a, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x4c,
+ 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x55, 0x4c, 0x69, 0x6d,
+ 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x55, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x05, 0x55, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73,
+ 0x69, 0x6f, 0x6e, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52,
+ 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x26,
+ 0x0a, 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65,
+ 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
+ 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x78, 0x78, 0x43, 0x61, 0x6c,
+ 0x6c, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x43, 0x78, 0x78, 0x43, 0x61, 0x6c,
+ 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x43, 0x78, 0x78, 0x44, 0x75, 0x72, 0x4d, 0x6f, 0x72, 0x65,
+ 0x31, 0x30, 0x73, 0x65, 0x63, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x43, 0x78, 0x78,
+ 0x44, 0x75, 0x72, 0x4d, 0x6f, 0x72, 0x65, 0x31, 0x30, 0x73, 0x65, 0x63, 0x12, 0x28, 0x0a, 0x0f,
+ 0x43, 0x78, 0x78, 0x44, 0x75, 0x72, 0x4d, 0x6f, 0x72, 0x65, 0x33, 0x30, 0x73, 0x65, 0x63, 0x18,
+ 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x43, 0x78, 0x78, 0x44, 0x75, 0x72, 0x4d, 0x6f, 0x72,
+ 0x65, 0x33, 0x30, 0x73, 0x65, 0x63, 0x12, 0x24, 0x0a, 0x0d, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65,
+ 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x1e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x55,
+ 0x6e, 0x69, 0x71, 0x75, 0x65, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x73, 0x22, 0x11, 0x0a, 0x0f,
+ 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
+ 0x4d, 0x0a, 0x0d, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
+ 0x12, 0x1e, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x4c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x45, 0x78, 0x74,
+ 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x0c, 0x52, 0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x16,
+ 0x0a, 0x14, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61, 0x63, 0x68, 0x65, 0x73, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x12, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c,
+ 0x6c, 0x43, 0x61, 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x0f,
+ 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x53, 0x72, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x53, 0x72,
+ 0x63, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65,
+ 0x64, 0x4f, 0x62, 0x6a, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
+ 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x46, 0x69, 0x6c, 0x65, 0x73,
+ 0x32, 0xe4, 0x04, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74,
+ 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74,
+ 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6c, 0x69,
+ 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x17, 0x53, 0x74,
+ 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65,
+ 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61,
+ 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x73,
+ 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6e, 0x6f,
+ 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
+ 0x00, 0x12, 0x4d, 0x0a, 0x10, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x53,
+ 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x55, 0x70, 0x6c,
+ 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61,
+ 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01,
+ 0x12, 0x5c, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64,
+ 0x4f, 0x62, 0x6a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x6e, 0x6f, 0x63, 0x63,
+ 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x53, 0x74, 0x72, 0x65,
+ 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6e, 0x6f, 0x63, 0x63,
+ 0x2e, 0x52, 0x65, 0x63, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a,
+ 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3e,
+ 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x17, 0x2e, 0x6e,
+ 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x6f,
+ 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x32,
+ 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x13, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e,
+ 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e,
+ 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
+ 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x08, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x15,
+ 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x44, 0x75, 0x6d, 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x44, 0x75, 0x6d,
+ 0x70, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x47,
+ 0x0a, 0x0d, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61, 0x63, 0x68, 0x65, 0x73, 0x12,
+ 0x1a, 0x2e, 0x6e, 0x6f, 0x63, 0x63, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61,
+ 0x63, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6e, 0x6f,
+ 0x63, 0x63, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x43, 0x61, 0x63, 0x68, 0x65, 0x73,
+ 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x1a, 0x5a, 0x18, 0x67, 0x69, 0x74, 0x68, 0x75,
+ 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x56, 0x4b, 0x43, 0x4f, 0x4d, 0x2f, 0x6e, 0x6f, 0x63, 0x63,
+ 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/pb/nocc-protobuf.proto b/pb/nocc-protobuf.proto
index c7053da..b66a89d 100644
--- a/pb/nocc-protobuf.proto
+++ b/pb/nocc-protobuf.proto
@@ -33,6 +33,7 @@ message StartClientRequest {
string HostUserName = 2;
string ClientVersion = 3;
bool DisableObjCache = 10;
+ string AllRemotesDelim = 20;
}
message StartClientReply {
@@ -98,6 +99,14 @@ message StatusReply {
int64 LogFileSize = 6;
int64 SrcCacheSize = 7;
int64 ObjCacheSize = 8;
+ int64 ULimit = 9;
+ string UName = 10;
+ int64 SessionsTotal = 11;
+ int64 SessionsActive = 12;
+ int64 CxxCalls = 20;
+ int64 CxxDurMore10sec = 21;
+ int64 CxxDurMore30sec = 22;
+ repeated string UniqueRemotes = 30;
}
message DumpLogsRequest {