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 {