diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index f2765a3ec97..d41907f76ff 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -13,6 +13,9 @@ AIBridge = "AI Bridge" [default.extend-words] AKS = "AKS" +# BRIN is a postgres index type +brin = "brin" +BRIN = "BRIN" # do as sudo replacement doas = "doas" darcula = "darcula" diff --git a/agent/agent.go b/agent/agent.go index b3a9fa6f48f..a46e32dde12 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -58,6 +58,7 @@ import ( "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/cli/gitauth" "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/idemetadata" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/workspacesdk" @@ -127,23 +128,14 @@ type Options struct { } type Client interface { - ConnectRPC29(ctx context.Context) ( - proto.DRPCAgentClient29, tailnetproto.DRPCTailnetClient28, error, + ConnectRPC211(ctx context.Context) ( + proto.DRPCAgentClient211, tailnetproto.DRPCTailnetClient28, error, ) - // ConnectRPC29WithRole is like ConnectRPC29 but sends an explicit + // ConnectRPC211WithRole is like ConnectRPC211 but sends an explicit // role query parameter to the server. The workspace agent should // use role "agent" to enable connection monitoring. - ConnectRPC29WithRole(ctx context.Context, role string) ( - proto.DRPCAgentClient29, tailnetproto.DRPCTailnetClient28, error, - ) - ConnectRPC210(ctx context.Context) ( - proto.DRPCAgentClient210, tailnetproto.DRPCTailnetClient28, error, - ) - // ConnectRPC210WithRole is like ConnectRPC210 but sends an explicit - // role query parameter to the server. The workspace agent should - // use role "agent" to enable connection monitoring. - ConnectRPC210WithRole(ctx context.Context, role string) ( - proto.DRPCAgentClient210, tailnetproto.DRPCTailnetClient28, error, + ConnectRPC211WithRole(ctx context.Context, role string) ( + proto.DRPCAgentClient211, tailnetproto.DRPCTailnetClient28, error, ) tailnet.DERPMapRewriter agentsdk.RefreshableSessionTokenProvider @@ -426,17 +418,15 @@ func (a *agent) init() { BlockLocalPortForwarding: a.blockLocalPortForwarding, ReportConnection: func(id uuid.UUID, magicType agentssh.MagicSessionType, ip string) func(code int, reason string) { var connectionType proto.Connection_Type - switch magicType { - case agentssh.MagicSessionTypeSSH: + // The enum cannot hold arbitrary types, so map by family. + switch idemetadata.Family(string(magicType)) { + case idemetadata.AppNameSSH: connectionType = proto.Connection_SSH - case agentssh.MagicSessionTypeVSCode: + case idemetadata.AppNameVSCode: connectionType = proto.Connection_VSCODE - case agentssh.MagicSessionTypeJetBrains: + case idemetadata.AppNameJetBrains: connectionType = proto.Connection_JETBRAINS - case agentssh.MagicSessionTypeUnknown: - connectionType = proto.Connection_TYPE_UNSPECIFIED default: - a.logger.Error(a.hardCtx, "unhandled magic session type when reporting connection", slog.F("magic_type", magicType)) connectionType = proto.Connection_TYPE_UNSPECIFIED } @@ -1176,7 +1166,7 @@ func (a *agent) run() (retErr error) { // ConnectRPC returns the dRPC connection we use for the Agent and Tailnet v2+ APIs. // We pass role "agent" to enable connection monitoring on the server, which tracks // the agent's connectivity state (first_connected_at, last_connected_at, disconnected_at). - aAPI, tAPI, err := a.client.ConnectRPC210WithRole(a.hardCtx, "agent") + aAPI, tAPI, err := a.client.ConnectRPC211WithRole(a.hardCtx, "agent") if err != nil { return err } @@ -2162,13 +2152,12 @@ func (a *agent) Collect(ctx context.Context, networkStats map[netlogtype.Connect stats.TxPackets += int64(counts.TxPackets) } - // The count of active sessions. - sshStats := a.sshServer.ConnStats() - stats.SessionCountSsh = sshStats.Sessions - stats.SessionCountVscode = sshStats.VSCode - stats.SessionCountJetbrains = sshStats.JetBrains - - stats.SessionCountReconnectingPty = a.reconnectingPTYServer.ConnCount() + // Active sessions per app; the deprecated fields stay zero. A client may + // label an ssh session "reconnecting_pty", so add, don't overwrite. + stats.SessionCounts = a.sshServer.SessionCounts() + if count := a.reconnectingPTYServer.ConnCount(); count > 0 { + stats.SessionCounts[idemetadata.AppNameReconnectingPTY] += count + } // Compute the median connection latency! a.logger.Debug(ctx, "starting peer latency measurement for stats") diff --git a/agent/agent_test.go b/agent/agent_test.go index 908ce0ed3d8..a51c842168b 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -229,7 +229,7 @@ func assertSSHStats(t *testing.T, stats <-chan *proto.Stats) { return false } t.Logf("got stats: ConnectionCount=%d, RxBytes=%d, TxBytes=%d, SessionCountSsh=%d", - s.ConnectionCount, s.RxBytes, s.TxBytes, s.SessionCountSsh) + s.ConnectionCount, s.RxBytes, s.TxBytes, s.SessionCounts["ssh"]) if s.ConnectionCount > 0 { connectionCountSeen = true } @@ -239,7 +239,7 @@ func assertSSHStats(t *testing.T, stats <-chan *proto.Stats) { if s.TxBytes > 0 { txBytesSeen = true } - if s.SessionCountSsh == 1 { + if s.SessionCounts["ssh"] == 1 { sessionCountSSHSeen = true } return connectionCountSeen && rxBytesSeen && txBytesSeen && sessionCountSSHSeen @@ -287,7 +287,7 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) { if s.TxBytes > 0 { txBytesSeen = true } - if s.SessionCountReconnectingPty == 1 { + if s.SessionCounts["reconnecting_pty"] == 1 { sessionCountReconnectingPTYSeen = true } return connectionCountSeen && rxBytesSeen && txBytesSeen && sessionCountReconnectingPTYSeen @@ -346,12 +346,12 @@ func TestAgent_Stats_Magic(t *testing.T) { require.NoError(t, err) require.Eventuallyf(t, func() bool { s, ok := <-stats - t.Logf("got stats: ok=%t, ConnectionCount=%d, RxBytes=%d, TxBytes=%d, SessionCountVSCode=%d, ConnectionMedianLatencyMS=%f", - ok, s.ConnectionCount, s.RxBytes, s.TxBytes, s.SessionCountVscode, s.ConnectionMedianLatencyMs) + t.Logf("got stats: ok=%t, ConnectionCount=%d, RxBytes=%d, TxBytes=%d, SessionCounts[vscode]=%d, ConnectionMedianLatencyMS=%f", + ok, s.ConnectionCount, s.RxBytes, s.TxBytes, s.SessionCounts["vscode"], s.ConnectionMedianLatencyMs) return ok && // Ensure that the connection didn't count as a "normal" SSH session. // This was a special one, so it should be labeled specially in the stats! - s.SessionCountVscode == 1 && + s.SessionCounts["vscode"] == 1 && // Ensure that connection latency is being counted! // If it isn't, it's set to -1. s.ConnectionMedianLatencyMs >= 0 @@ -417,8 +417,8 @@ func TestAgent_Stats_Magic(t *testing.T) { require.Eventuallyf(t, func() bool { s, ok := <-stats t.Logf("got stats with conn open: ok=%t, ConnectionCount=%d, SessionCountJetBrains=%d", - ok, s.ConnectionCount, s.SessionCountJetbrains) - return ok && s.SessionCountJetbrains == 1 + ok, s.ConnectionCount, s.SessionCounts["jetbrains"]) + return ok && s.SessionCounts["jetbrains"] == 1 }, testutil.WaitLong, testutil.IntervalFast, "never saw stats with conn open", ) @@ -431,9 +431,9 @@ func TestAgent_Stats_Magic(t *testing.T) { require.Eventuallyf(t, func() bool { s, ok := <-stats t.Logf("got stats after disconnect %t, %d", - ok, s.SessionCountJetbrains) + ok, s.SessionCounts["jetbrains"]) return ok && - s.SessionCountJetbrains == 0 + s.SessionCounts["jetbrains"] == 0 }, testutil.WaitLong, testutil.IntervalFast, "never saw stats after conn closes", ) diff --git a/agent/agentssh/agentssh.go b/agent/agentssh/agentssh.go index eb2e9ebb6bf..8b9586867ba 100644 --- a/agent/agentssh/agentssh.go +++ b/agent/agentssh/agentssh.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "maps" "net" "os" "os/exec" @@ -31,6 +32,7 @@ import ( "github.com/coder/coder/v2/agent/agentexec" "github.com/coder/coder/v2/agent/agentrsa" "github.com/coder/coder/v2/agent/usershell" + "github.com/coder/coder/v2/coderd/idemetadata" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/pty" ) @@ -71,17 +73,16 @@ const ( ContainerUserEnvironmentVariable = "CODER_CONTAINER_USER" ) -// MagicSessionType enums. +// Well-known magic session types, defined as canonical app names so the +// agent and server vocabularies cannot drift. const ( - // MagicSessionTypeUnknown means the session type could not be determined. - MagicSessionTypeUnknown MagicSessionType = "unknown" // MagicSessionTypeSSH is the default session type. - MagicSessionTypeSSH MagicSessionType = "ssh" + MagicSessionTypeSSH MagicSessionType = idemetadata.AppNameSSH // MagicSessionTypeVSCode is set in the SSH config by the VS Code extension to identify itself. - MagicSessionTypeVSCode MagicSessionType = "vscode" + MagicSessionTypeVSCode MagicSessionType = idemetadata.AppNameVSCode // MagicSessionTypeJetBrains is set in the SSH config by the JetBrains // extension to identify itself. - MagicSessionTypeJetBrains MagicSessionType = "jetbrains" + MagicSessionTypeJetBrains MagicSessionType = idemetadata.AppNameJetBrains ) // BlockedFileTransferCommands contains a list of restricted file transfer commands. @@ -148,6 +149,11 @@ type Server struct { // a lock on mu but protected by closing. wg sync.WaitGroup + // Active sessions per session type, zero-count entries removed. Kept off + // mu, which Close holds while closing sessions. + sessionCountsMu sync.RWMutex + sessionCounts map[string]int64 + Execer agentexec.Execer logger slog.Logger srv *ssh.Server @@ -155,10 +161,6 @@ type Server struct { config *Config - connCountVSCode atomic.Int64 - connCountJetBrains atomic.Int64 - connCountSSHSession atomic.Int64 - metrics *sshServerMetrics } @@ -200,13 +202,14 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom metrics := newSSHServerMetrics(prometheusRegistry) s := &Server{ - Execer: execer, - listeners: make(map[net.Listener]struct{}), - fs: fs, - conns: make(map[net.Conn]struct{}), - sessions: make(map[ssh.Session]struct{}), - processes: make(map[*os.Process]struct{}), - logger: logger, + Execer: execer, + listeners: make(map[net.Listener]struct{}), + fs: fs, + conns: make(map[net.Conn]struct{}), + sessions: make(map[ssh.Session]struct{}), + processes: make(map[*os.Process]struct{}), + sessionCounts: make(map[string]int64), + logger: logger, config: config, @@ -232,7 +235,7 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom ChannelHandlers: map[string]ssh.ChannelHandler{ "direct-tcpip": func(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) { // Wrapper is designed to find and track JetBrains Gateway connections. - wrapped := NewJetbrainsChannelWatcher(ctx, s.logger, s.config.ReportConnection, newChan, &s.connCountJetBrains) + wrapped := NewJetbrainsChannelWatcher(ctx, s.logger, s.config.ReportConnection, newChan, s.startSession) ssh.DirectTCPIPHandler(srv, conn, wrapped, ctx) }, "direct-streamlocal@openssh.com": func(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) { @@ -324,18 +327,30 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom return s, nil } -type ConnStats struct { - Sessions int64 - VSCode int64 - JetBrains int64 +// startSession increments the count for the given session type and returns a +// function that decrements it. Counters are created on demand; entries are +// bounded by concurrent sessions and capped again at ingestion. +func (s *Server) startSession(magicType MagicSessionType) (endSession func()) { + key := idemetadata.Normalize(string(magicType)) + s.sessionCountsMu.Lock() + defer s.sessionCountsMu.Unlock() + s.sessionCounts[key]++ + return func() { + s.sessionCountsMu.Lock() + defer s.sessionCountsMu.Unlock() + s.sessionCounts[key]-- + if s.sessionCounts[key] <= 0 { + delete(s.sessionCounts, key) + } + } } -func (s *Server) ConnStats() ConnStats { - return ConnStats{ - Sessions: s.connCountSSHSession.Load(), - VSCode: s.connCountVSCode.Load(), - JetBrains: s.connCountJetBrains.Load(), - } +// SessionCounts returns a snapshot of active sessions per session type. Never +// nil, so callers can merge other session sources into it. +func (s *Server) SessionCounts() map[string]int64 { + s.sessionCountsMu.RLock() + defer s.sessionCountsMu.RUnlock() + return maps.Clone(s.sessionCounts) } func extractMagicSessionType(env []string) (magicType MagicSessionType, rawType string, filteredEnv []string) { @@ -348,16 +363,11 @@ func extractMagicSessionType(env []string) (magicType MagicSessionType, rawType // Keep going, we'll use the last instance of the env. } - // Always force lowercase checking to be case-insensitive. - switch MagicSessionType(strings.ToLower(rawType)) { - case MagicSessionTypeVSCode: - magicType = MagicSessionTypeVSCode - case MagicSessionTypeJetBrains: - magicType = MagicSessionTypeJetBrains - case "", MagicSessionTypeSSH: + if rawType == "" { magicType = MagicSessionTypeSSH - default: - magicType = MagicSessionTypeUnknown + } else { + // Canonicalize, don't classify: unknown names flow through. + magicType = MagicSessionType(idemetadata.Normalize(rawType)) } return magicType, rawType, slices.DeleteFunc(env, func(kv string) bool { @@ -424,6 +434,13 @@ func (s *Server) sessionHandler(session ssh.Session) { env := session.Environ() magicType, magicTypeRaw, env := extractMagicSessionType(env) + magicTypeFamily := idemetadata.Family(string(magicType)) + if magicTypeFamily == idemetadata.AppNameUnknown { + logger.Debug(ctx, "unrecognized ssh session type", + slog.F("magic_type", magicType), + slog.F("raw_type", magicTypeRaw), + ) + } // It's not safe to assume RemoteAddr() returns a non-nil value. slog.F usage is fine because it correctly // handles nil. @@ -449,19 +466,13 @@ func (s *Server) sessionHandler(session ssh.Session) { reportSession := true - switch magicType { - case MagicSessionTypeVSCode: - s.connCountVSCode.Add(1) - defer s.connCountVSCode.Add(-1) - case MagicSessionTypeJetBrains: + if magicTypeFamily == idemetadata.AppNameJetBrains { // Do nothing here because JetBrains launches hundreds of ssh sessions. // We instead track JetBrains in the single persistent tcp forwarding channel. reportSession = false - case MagicSessionTypeSSH: - s.connCountSSHSession.Add(1) - defer s.connCountSSHSession.Add(-1) - case MagicSessionTypeUnknown: - logger.Warn(ctx, "invalid magic ssh session type specified", slog.F("raw_type", magicTypeRaw)) + } else { + endSession := s.startSession(magicType) + defer endSession() } closeCause := func(_ string) {} diff --git a/agent/agentssh/jetbrainstrack.go b/agent/agentssh/jetbrainstrack.go index 2ea54b54306..39ded212cd2 100644 --- a/agent/agentssh/jetbrainstrack.go +++ b/agent/agentssh/jetbrainstrack.go @@ -7,7 +7,6 @@ import ( "github.com/gliderlabs/ssh" "github.com/google/uuid" - "go.uber.org/atomic" gossh "golang.org/x/crypto/ssh" "cdr.dev/slog/v3" @@ -26,15 +25,18 @@ type localForwardChannelData struct { // JetbrainsChannelWatcher is used to track JetBrains port forwarded (Gateway) // channels. If the port forward is something other than JetBrains, this struct // is a noop. +// +// Sessions are counted here, not in sessionHandler: JetBrains opens hundreds +// of ssh sessions but only one persistent forwarded channel. type JetbrainsChannelWatcher struct { gossh.NewChannel - jetbrainsCounter *atomic.Int64 + startSession func(MagicSessionType) (endSession func()) logger slog.Logger originAddr string reportConnection reportConnectionFunc } -func NewJetbrainsChannelWatcher(ctx ssh.Context, logger slog.Logger, reportConnection reportConnectionFunc, newChannel gossh.NewChannel, counter *atomic.Int64) gossh.NewChannel { +func NewJetbrainsChannelWatcher(ctx ssh.Context, logger slog.Logger, reportConnection reportConnectionFunc, newChannel gossh.NewChannel, startSession func(MagicSessionType) (endSession func())) gossh.NewChannel { d := localForwardChannelData{} if err := gossh.Unmarshal(newChannel.ExtraData(), &d); err != nil { // If the data fails to unmarshal, do nothing. @@ -63,7 +65,7 @@ func NewJetbrainsChannelWatcher(ctx ssh.Context, logger slog.Logger, reportConne return &JetbrainsChannelWatcher{ NewChannel: newChannel, - jetbrainsCounter: counter, + startSession: startSession, logger: logger.With(slog.F("destination_port", d.DestPort)), originAddr: d.OriginAddr, reportConnection: reportConnection, @@ -78,14 +80,14 @@ func (w *JetbrainsChannelWatcher) Accept() (gossh.Channel, <-chan *gossh.Request disconnected(1, err.Error()) return c, r, err } - w.jetbrainsCounter.Add(1) + endSession := w.startSession(MagicSessionTypeJetBrains) // nolint: gocritic // JetBrains is a proper noun and should be capitalized w.logger.Debug(context.Background(), "JetBrains watcher accepted channel") return &ChannelOnClose{ Channel: c, done: func() { - w.jetbrainsCounter.Add(-1) + endSession() disconnected(0, "normal close") // nolint: gocritic // JetBrains is a proper noun and should be capitalized w.logger.Debug(context.Background(), "JetBrains channel closed", diff --git a/agent/agentssh/metrics.go b/agent/agentssh/metrics.go index 22bbf1fd807..e2bb3c51310 100644 --- a/agent/agentssh/metrics.go +++ b/agent/agentssh/metrics.go @@ -1,9 +1,9 @@ package agentssh import ( - "strings" - "github.com/prometheus/client_golang/prometheus" + + "github.com/coder/coder/v2/coderd/idemetadata" ) type sshServerMetrics struct { @@ -47,6 +47,7 @@ func newSSHServerMetrics(registerer prometheus.Registerer) *sshServerMetrics { Subsystem: "sessions", Name: "total", }, + // magic_type is the app family, see magicTypeMetricLabel. []string{"magic_type", "pty"}, ) registerer.MustRegister(sessionsTotal) @@ -72,14 +73,6 @@ func newSSHServerMetrics(registerer prometheus.Registerer) *sshServerMetrics { } func magicTypeMetricLabel(magicType MagicSessionType) string { - switch magicType { - case MagicSessionTypeVSCode: - case MagicSessionTypeJetBrains: - case MagicSessionTypeSSH: - case MagicSessionTypeUnknown: - default: - magicType = MagicSessionTypeUnknown - } - // Always be case insensitive - return strings.ToLower(string(magicType)) + // Family, not raw type, so cardinality stays bounded. + return idemetadata.Family(string(magicType)) } diff --git a/agent/agentssh/sessiontype_internal_test.go b/agent/agentssh/sessiontype_internal_test.go new file mode 100644 index 00000000000..222791105a7 --- /dev/null +++ b/agent/agentssh/sessiontype_internal_test.go @@ -0,0 +1,90 @@ +package agentssh + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/idemetadata" +) + +func TestExtractMagicSessionType(t *testing.T) { + t.Parallel() + + envWith := func(value string) []string { + return []string{ + "FOO=bar", + fmt.Sprintf("%s=%s", MagicSessionTypeEnvironmentVariable, value), + "BAZ=qux", + } + } + + for _, tc := range []struct { + name string + env []string + want MagicSessionType + }{ + {"NoEnvDefaultsToSSH", []string{"FOO=bar"}, MagicSessionTypeSSH}, + {"EmptyValueDefaultsToSSH", envWith(""), MagicSessionTypeSSH}, + {"VSCode", envWith("vscode"), MagicSessionTypeVSCode}, + {"JetBrainsLegacyCasing", envWith("JetBrains"), MagicSessionTypeJetBrains}, + {"UnknownTypeCanonicalized", envWith("Cursor-Nightly"), MagicSessionType("cursor_nightly")}, + {"LastInstanceWins", append(envWith("vscode"), MagicSessionTypeEnvironmentVariable+"=cursor"), MagicSessionType("cursor")}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + sessionType, _, filteredEnv := extractMagicSessionType(tc.env) + require.Equal(t, tc.want, sessionType) + for _, kv := range filteredEnv { + require.NotContains(t, kv, MagicSessionTypeEnvironmentVariable+"=") + } + }) + } +} + +func TestMagicTypeMetricLabel(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + magicType MagicSessionType + want string + }{ + {"SSH", MagicSessionTypeSSH, idemetadata.AppNameSSH}, + {"ForkUsesFamily", MagicSessionType("cursor"), idemetadata.AppNameVSCode}, + // No alias for the nightly variant, so it does not get a label. + {"UnlistedVariant", MagicSessionType("cursor_nightly"), idemetadata.AppNameUnknown}, + {"Unknown", MagicSessionType("some_new_ide"), idemetadata.AppNameUnknown}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, magicTypeMetricLabel(tc.magicType)) + }) + } +} + +func TestSessionCounts(t *testing.T) { + t.Parallel() + + s := &Server{sessionCounts: make(map[string]int64)} + + // Counters are created on demand and names are canonicalized. + _ = s.startSession(MagicSessionTypeSSH) + _ = s.startSession(MagicSessionTypeVSCode) + endCursor1 := s.startSession(MagicSessionType("Cursor")) + endCursor2 := s.startSession(MagicSessionType("cursor")) + require.Equal(t, map[string]int64{ + idemetadata.AppNameSSH: 1, + idemetadata.AppNameVSCode: 1, + "cursor": 2, + }, s.SessionCounts()) + + // Zero-count entries are dropped, not reported as idle apps. + endCursor1() + endCursor2() + require.Equal(t, map[string]int64{ + idemetadata.AppNameSSH: 1, + idemetadata.AppNameVSCode: 1, + }, s.SessionCounts()) +} diff --git a/agent/agenttest/client.go b/agent/agenttest/client.go index 0f5d83a98f9..9f86b0540e4 100644 --- a/agent/agenttest/client.go +++ b/agent/agenttest/client.go @@ -158,8 +158,8 @@ func (c *Client) ConnectRPC29WithRole(ctx context.Context, _ string) ( return c.ConnectRPC29(ctx) } -func (c *Client) ConnectRPC210(ctx context.Context) ( - agentproto.DRPCAgentClient210, proto.DRPCTailnetClient28, error, +func (c *Client) ConnectRPC211(ctx context.Context) ( + agentproto.DRPCAgentClient211, proto.DRPCTailnetClient28, error, ) { aAPI, tAPI, err := c.ConnectRPC29(ctx) if err != nil { @@ -169,17 +169,17 @@ func (c *Client) ConnectRPC210(ctx context.Context) ( // the generated DRPCAgentClient interface, including // PushContextState, so the assertion always succeeds for // the fixture's own connections. - client, ok := aAPI.(agentproto.DRPCAgentClient210) + client, ok := aAPI.(agentproto.DRPCAgentClient211) if !ok { - return nil, nil, xerrors.Errorf("agenttest: connection does not implement DRPCAgentClient210; got %T", aAPI) + return nil, nil, xerrors.Errorf("agenttest: connection does not implement DRPCAgentClient211; got %T", aAPI) } return client, tAPI, nil } -func (c *Client) ConnectRPC210WithRole(ctx context.Context, _ string) ( - agentproto.DRPCAgentClient210, proto.DRPCTailnetClient28, error, +func (c *Client) ConnectRPC211WithRole(ctx context.Context, _ string) ( + agentproto.DRPCAgentClient211, proto.DRPCTailnetClient28, error, ) { - return c.ConnectRPC210(ctx) + return c.ConnectRPC211(ctx) } func (c *Client) ConnectRPC29(ctx context.Context) ( diff --git a/agent/proto/agent.pb.go b/agent/proto/agent.pb.go index 774504cda22..494376b7f91 100644 --- a/agent/proto/agent.pb.go +++ b/agent/proto/agent.pb.go @@ -1705,17 +1705,39 @@ type Stats struct { TxBytes int64 `protobuf:"varint,7,opt,name=tx_bytes,json=txBytes,proto3" json:"tx_bytes,omitempty"` // SessionCountVSCode is the number of connections received by an agent // that are from our VS Code extension. + // + // Deprecated: Use session_counts instead. Only set by old agents; the + // server converts these fields into the map when the map is empty. + // + // Deprecated: Marked as deprecated in agent/proto/agent.proto. SessionCountVscode int64 `protobuf:"varint,8,opt,name=session_count_vscode,json=sessionCountVscode,proto3" json:"session_count_vscode,omitempty"` // SessionCountJetBrains is the number of connections received by an agent // that are from our JetBrains extension. + // + // Deprecated: Use session_counts instead. + // + // Deprecated: Marked as deprecated in agent/proto/agent.proto. SessionCountJetbrains int64 `protobuf:"varint,9,opt,name=session_count_jetbrains,json=sessionCountJetbrains,proto3" json:"session_count_jetbrains,omitempty"` // SessionCountReconnectingPTY is the number of connections received by an agent // that are from the reconnecting web terminal. + // + // Deprecated: Use session_counts instead. + // + // Deprecated: Marked as deprecated in agent/proto/agent.proto. SessionCountReconnectingPty int64 `protobuf:"varint,10,opt,name=session_count_reconnecting_pty,json=sessionCountReconnectingPty,proto3" json:"session_count_reconnecting_pty,omitempty"` // SessionCountSSH is the number of connections received by an agent // that are normal, non-tagged SSH sessions. + // + // Deprecated: Use session_counts instead. + // + // Deprecated: Marked as deprecated in agent/proto/agent.proto. SessionCountSsh int64 `protobuf:"varint,11,opt,name=session_count_ssh,json=sessionCountSsh,proto3" json:"session_count_ssh,omitempty"` Metrics []*Stats_Metric `protobuf:"bytes,12,rep,name=metrics,proto3" json:"metrics,omitempty"` + // SessionCounts is the number of sessions per app, keyed by the app + // identifier reported by the client, normalized at ingestion (e.g. + // "vscode", "jetbrains", "ssh", "reconnecting_pty"). Replaces the + // deprecated session_count_* fields. + SessionCounts map[string]int64 `protobuf:"bytes,13,rep,name=session_counts,json=sessionCounts,proto3" json:"session_counts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` } func (x *Stats) Reset() { @@ -1799,6 +1821,7 @@ func (x *Stats) GetTxBytes() int64 { return 0 } +// Deprecated: Marked as deprecated in agent/proto/agent.proto. func (x *Stats) GetSessionCountVscode() int64 { if x != nil { return x.SessionCountVscode @@ -1806,6 +1829,7 @@ func (x *Stats) GetSessionCountVscode() int64 { return 0 } +// Deprecated: Marked as deprecated in agent/proto/agent.proto. func (x *Stats) GetSessionCountJetbrains() int64 { if x != nil { return x.SessionCountJetbrains @@ -1813,6 +1837,7 @@ func (x *Stats) GetSessionCountJetbrains() int64 { return 0 } +// Deprecated: Marked as deprecated in agent/proto/agent.proto. func (x *Stats) GetSessionCountReconnectingPty() int64 { if x != nil { return x.SessionCountReconnectingPty @@ -1820,6 +1845,7 @@ func (x *Stats) GetSessionCountReconnectingPty() int64 { return 0 } +// Deprecated: Marked as deprecated in agent/proto/agent.proto. func (x *Stats) GetSessionCountSsh() int64 { if x != nil { return x.SessionCountSsh @@ -1834,6 +1860,13 @@ func (x *Stats) GetMetrics() []*Stats_Metric { return nil } +func (x *Stats) GetSessionCounts() map[string]int64 { + if x != nil { + return x.SessionCounts + } + return nil +} + type UpdateStatsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4774,7 +4807,7 @@ type Stats_Metric_Label struct { func (x *Stats_Metric_Label) Reset() { *x = Stats_Metric_Label{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[62] + mi := &file_agent_proto_agent_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4787,7 +4820,7 @@ func (x *Stats_Metric_Label) String() string { func (*Stats_Metric_Label) ProtoMessage() {} func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[62] + mi := &file_agent_proto_agent_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4829,7 +4862,7 @@ type BatchUpdateAppHealthRequest_HealthUpdate struct { func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() { *x = BatchUpdateAppHealthRequest_HealthUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[63] + mi := &file_agent_proto_agent_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4842,7 +4875,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string { func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[63] + mi := &file_agent_proto_agent_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4884,7 +4917,7 @@ type GetResourcesMonitoringConfigurationResponse_Config struct { func (x *GetResourcesMonitoringConfigurationResponse_Config) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Config{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[64] + mi := &file_agent_proto_agent_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4897,7 +4930,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Config) String() string { func (*GetResourcesMonitoringConfigurationResponse_Config) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Config) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[64] + mi := &file_agent_proto_agent_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4938,7 +4971,7 @@ type GetResourcesMonitoringConfigurationResponse_Memory struct { func (x *GetResourcesMonitoringConfigurationResponse_Memory) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Memory{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[65] + mi := &file_agent_proto_agent_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4951,7 +4984,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Memory) String() string { func (*GetResourcesMonitoringConfigurationResponse_Memory) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Memory) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[65] + mi := &file_agent_proto_agent_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4986,7 +5019,7 @@ type GetResourcesMonitoringConfigurationResponse_Volume struct { func (x *GetResourcesMonitoringConfigurationResponse_Volume) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Volume{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[66] + mi := &file_agent_proto_agent_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4999,7 +5032,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Volume) String() string { func (*GetResourcesMonitoringConfigurationResponse_Volume) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Volume) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[66] + mi := &file_agent_proto_agent_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5042,7 +5075,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[67] + mi := &file_agent_proto_agent_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5055,7 +5088,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint) String() string { func (*PushResourcesMonitoringUsageRequest_Datapoint) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[67] + mi := &file_agent_proto_agent_proto_msgTypes[68] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5104,7 +5137,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[68] + mi := &file_agent_proto_agent_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5117,7 +5150,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) String() str func (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[68] + mi := &file_agent_proto_agent_proto_msgTypes[69] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5160,7 +5193,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[69] + mi := &file_agent_proto_agent_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5173,7 +5206,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) String() str func (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[69] + mi := &file_agent_proto_agent_proto_msgTypes[70] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5233,7 +5266,7 @@ type CreateSubAgentRequest_App struct { func (x *CreateSubAgentRequest_App) Reset() { *x = CreateSubAgentRequest_App{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[70] + mi := &file_agent_proto_agent_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5246,7 +5279,7 @@ func (x *CreateSubAgentRequest_App) String() string { func (*CreateSubAgentRequest_App) ProtoMessage() {} func (x *CreateSubAgentRequest_App) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[70] + mi := &file_agent_proto_agent_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5366,7 +5399,7 @@ type CreateSubAgentRequest_App_Healthcheck struct { func (x *CreateSubAgentRequest_App_Healthcheck) Reset() { *x = CreateSubAgentRequest_App_Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[71] + mi := &file_agent_proto_agent_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5379,7 +5412,7 @@ func (x *CreateSubAgentRequest_App_Healthcheck) String() string { func (*CreateSubAgentRequest_App_Healthcheck) ProtoMessage() {} func (x *CreateSubAgentRequest_App_Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[71] + mi := &file_agent_proto_agent_proto_msgTypes[72] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5429,7 +5462,7 @@ type CreateSubAgentResponse_AppCreationError struct { func (x *CreateSubAgentResponse_AppCreationError) Reset() { *x = CreateSubAgentResponse_AppCreationError{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[72] + mi := &file_agent_proto_agent_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5442,7 +5475,7 @@ func (x *CreateSubAgentResponse_AppCreationError) String() string { func (*CreateSubAgentResponse_AppCreationError) ProtoMessage() {} func (x *CreateSubAgentResponse_AppCreationError) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[72] + mi := &file_agent_proto_agent_proto_msgTypes[73] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5495,7 +5528,7 @@ type BoundaryLog_HttpRequest struct { func (x *BoundaryLog_HttpRequest) Reset() { *x = BoundaryLog_HttpRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[73] + mi := &file_agent_proto_agent_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5508,7 +5541,7 @@ func (x *BoundaryLog_HttpRequest) String() string { func (*BoundaryLog_HttpRequest) ProtoMessage() {} func (x *BoundaryLog_HttpRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[73] + mi := &file_agent_proto_agent_proto_msgTypes[74] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5760,7 +5793,7 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xb3, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, + 0x74, 0x22, 0xd6, 0x08, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, @@ -5780,692 +5813,702 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x14, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x76, 0x73, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x36, - 0x0a, 0x17, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, - 0x6a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x15, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, - 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, + 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x17, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x6a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x15, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x47, + 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, + 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x74, 0x79, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x1b, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, - 0x45, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x8e, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, + 0x4f, 0x0a, 0x0e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x1a, 0x45, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, + 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x8e, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x31, - 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0x34, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, - 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x22, 0x41, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, + 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, + 0x31, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0x34, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, + 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x41, 0x0a, 0x12, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x59, 0x0a, + 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x66, + 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, 0x74, 0x22, 0xae, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, + 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, + 0x4e, 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, + 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, + 0x59, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, + 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, + 0x57, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, + 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, + 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x46, 0x46, 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, + 0x65, 0x52, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc4, 0x01, 0x0a, + 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, + 0x1a, 0x51, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x22, 0x1e, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xe8, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, + 0x61, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x44, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x59, 0x0a, 0x13, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, - 0x63, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x41, 0x74, 0x22, 0xae, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, - 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, - 0x55, 0x54, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x05, - 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, - 0x4e, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, - 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x55, - 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, 0x12, 0x07, 0x0a, - 0x03, 0x4f, 0x46, 0x46, 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, - 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc4, 0x01, 0x0a, 0x1b, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x51, 0x0a, - 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, - 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, - 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x22, 0x1e, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0xe8, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, - 0x65, 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x75, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x73, 0x75, - 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x42, 0x53, 0x59, 0x53, 0x54, - 0x45, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x56, 0x42, 0x4f, 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, - 0x45, 0x4e, 0x56, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, - 0x45, 0x58, 0x45, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x03, 0x22, 0x49, 0x0a, 0x14, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x63, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x52, 0x0a, 0x1a, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, - 0x1d, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, - 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, - 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, - 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, - 0x52, 0x4e, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, - 0x65, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, - 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, + 0x61, 0x72, 0x74, 0x75, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, + 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x53, + 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x42, 0x53, + 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x56, 0x42, 0x4f, 0x58, 0x10, 0x01, 0x12, + 0x0e, 0x0a, 0x0a, 0x45, 0x4e, 0x56, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, + 0x0d, 0x0a, 0x09, 0x45, 0x58, 0x45, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x03, 0x22, 0x49, + 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, + 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x63, 0x0a, 0x08, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x52, + 0x0a, 0x1a, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x22, 0x1d, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0xde, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x05, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, - 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x47, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, - 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6c, - 0x6f, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, - 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, - 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, - 0x65, 0x72, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, - 0x6f, 0x72, 0x22, 0x56, 0x0a, 0x24, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x74, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, + 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x53, 0x0a, + 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, + 0x47, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x03, 0x12, 0x08, 0x0a, + 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x05, 0x22, 0x65, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, + 0x6c, 0x6f, 0x67, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x27, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x47, 0x0a, 0x17, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, + 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, + 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x13, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, + 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x56, 0x0a, 0x24, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, + 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x27, 0x0a, + 0x25, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xfd, 0x02, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, + 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x30, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, - 0x6e, 0x67, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x27, 0x0a, 0x25, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0xfd, 0x02, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, - 0x0a, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, - 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x65, - 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, - 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x26, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x09, 0x0a, 0x05, - 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, - 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x02, 0x22, 0x46, 0x0a, 0x06, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x10, 0x0a, - 0x0c, 0x45, 0x58, 0x49, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, - 0x0d, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, - 0x0a, 0x0f, 0x50, 0x49, 0x50, 0x45, 0x53, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x10, 0x03, 0x22, 0x2c, 0x0a, 0x2a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xa0, 0x04, 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, + 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x26, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, + 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, + 0x4f, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x02, 0x22, 0x46, + 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, + 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x49, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, + 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, + 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x49, 0x50, 0x45, 0x53, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, + 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x03, 0x22, 0x2c, 0x0a, 0x2a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0xa0, 0x04, 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x5a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5f, 0x0a, - 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x5c, - 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x6f, 0x0a, 0x06, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, - 0x6e, 0x75, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, - 0x1b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x19, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x22, 0x0a, - 0x06, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x1a, 0x36, 0x0a, 0x06, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, - 0x6d, 0x6f, 0x72, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x23, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, - 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0a, - 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0xac, 0x03, 0x0a, 0x09, - 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x66, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, - 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, - 0x12, 0x63, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x76, 0x6f, - 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x1a, 0x4f, - 0x0a, 0x0b, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x26, 0x0a, 0x24, 0x50, 0x75, - 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0xb6, 0x03, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0x3d, 0x0a, 0x06, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x02, 0x22, 0x56, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4a, - 0x45, 0x54, 0x42, 0x52, 0x41, 0x49, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, - 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x54, 0x59, 0x10, 0x04, - 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x55, 0x0a, 0x17, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x08, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x12, + 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x5f, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, + 0x01, 0x12, 0x5c, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, + 0x6f, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, + 0x12, 0x3e, 0x0a, 0x1b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x1a, 0x22, 0x0a, 0x06, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x1a, 0x36, 0x0a, 0x06, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, + 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x23, 0x50, 0x75, 0x73, 0x68, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x5d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0xac, + 0x03, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x66, 0x0a, 0x06, 0x6d, + 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, + 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, + 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, + 0x88, 0x01, 0x01, 0x12, 0x63, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x1a, 0x4f, 0x0a, 0x0b, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x26, 0x0a, + 0x24, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb6, 0x03, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0x3d, 0x0a, 0x06, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, + 0x0a, 0x07, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, + 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x02, 0x22, 0x56, 0x0a, 0x04, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, + 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0d, + 0x0a, 0x09, 0x4a, 0x45, 0x54, 0x42, 0x52, 0x41, 0x49, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x14, 0x0a, + 0x10, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x54, + 0x59, 0x10, 0x04, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x55, + 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x08, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xb9, 0x0a, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0xb9, 0x0a, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x22, 0x0a, - 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x3d, 0x0a, 0x04, - 0x61, 0x70, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x53, 0x0a, 0x0c, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x41, 0x70, 0x70, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, - 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x02, - 0x69, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x81, 0x07, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, - 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, - 0x67, 0x12, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x88, 0x01, 0x01, - 0x12, 0x26, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x48, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x88, - 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x08, 0x48, 0x05, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, - 0x17, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, - 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, - 0x5f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x48, 0x07, 0x52, 0x06, 0x6f, - 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x08, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x68, - 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x09, 0x52, 0x05, 0x73, 0x68, - 0x61, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x48, 0x0a, 0x52, 0x09, 0x73, 0x75, 0x62, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x75, 0x72, 0x6c, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0b, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x88, 0x01, 0x01, - 0x1a, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, - 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, - 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x22, 0x0a, 0x06, 0x4f, - 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, - 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, 0x10, 0x01, 0x22, - 0x4a, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, - 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, - 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x52, 0x47, - 0x41, 0x4e, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x42, - 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, - 0x63, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x42, - 0x08, 0x0a, 0x06, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x68, - 0x61, 0x72, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x6b, 0x0a, 0x0a, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, - 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, - 0x53, 0x49, 0x44, 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x57, 0x45, 0x42, 0x5f, - 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x53, - 0x48, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x4f, - 0x52, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x45, - 0x4c, 0x50, 0x45, 0x52, 0x10, 0x04, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x22, 0x96, 0x02, - 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x67, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, + 0x74, 0x75, 0x72, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, + 0x3d, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x53, + 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, + 0x70, 0x70, 0x73, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, + 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x81, 0x07, 0x0a, 0x03, 0x41, 0x70, 0x70, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x73, 0x6c, 0x75, 0x67, 0x12, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x48, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x88, + 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x06, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x07, 0x6f, + 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x48, 0x07, + 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x08, 0x52, 0x05, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, - 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x11, - 0x61, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x73, 0x1a, 0x63, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x05, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, - 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, - 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x49, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, + 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x09, 0x52, + 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x73, 0x75, 0x62, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x48, 0x0a, 0x52, 0x09, + 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0b, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x88, 0x01, 0x01, 0x1a, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x22, + 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, + 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, + 0x10, 0x01, 0x22, 0x4a, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, + 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, + 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, + 0x4f, 0x52, 0x47, 0x41, 0x4e, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x42, 0x0a, + 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42, 0x07, 0x0a, + 0x05, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, + 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x75, 0x62, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x6b, 0x0a, 0x0a, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x53, + 0x43, 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, + 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x57, + 0x45, 0x42, 0x5f, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0e, 0x0a, + 0x0a, 0x53, 0x53, 0x48, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1a, 0x0a, + 0x16, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x49, 0x4e, 0x47, + 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x04, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, + 0x22, 0x96, 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xb6, 0x02, 0x0a, - 0x0b, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x4c, 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6f, - 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, - 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, - 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x1a, 0x5a, 0x0a, - 0x0b, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, - 0x64, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x64, 0x52, 0x75, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x52, 0x04, - 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x5f, - 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe9, 0x01, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x73, 0x6c, 0x75, 0x67, 0x12, 0x4b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, - 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x42, 0x0a, - 0x0e, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0b, 0x0a, 0x07, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, - 0x49, 0x44, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, - 0x54, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, - 0x03, 0x22, 0x19, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8f, 0x05, 0x0a, - 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, - 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, - 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x12, 0x3e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x50, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, - 0x65, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x6b, 0x69, 0x6c, - 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x4d, 0x65, - 0x74, 0x61, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x05, 0x73, 0x6b, 0x69, 0x6c, 0x6c, 0x12, - 0x3e, 0x0a, 0x0a, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x43, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x6f, - 0x64, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x3e, 0x0a, 0x0a, 0x6d, 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x6f, - 0x64, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, - 0x61, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x56, 0x45, - 0x52, 0x53, 0x49, 0x5a, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, 0x52, 0x45, 0x41, - 0x44, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x43, 0x4c, 0x55, 0x44, 0x45, 0x44, - 0x10, 0x05, 0x42, 0x06, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, - 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, 0x08, 0x0e, - 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x4a, 0x04, 0x08, 0x10, 0x10, 0x11, 0x22, 0x2f, - 0x0a, 0x13, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, - 0x65, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, - 0x59, 0x0a, 0x0d, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x42, 0x6f, 0x64, 0x79, - 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0f, 0x0a, 0x0d, 0x4d, 0x43, - 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x0d, - 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, - 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x2d, 0x0a, 0x05, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x4d, 0x43, 0x50, 0x54, 0x6f, 0x6f, 0x6c, 0x52, 0x05, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x22, - 0x7b, 0x0a, 0x07, 0x4d, 0x43, 0x50, 0x54, 0x6f, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x3a, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, - 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0xe0, 0x01, 0x0a, - 0x17, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x67, 0x67, 0x72, - 0x65, 0x67, 0x61, 0x74, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3d, 0x0a, 0x09, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x69, 0x74, 0x69, - 0x61, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, - 0x36, 0x0a, 0x18, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, - 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, - 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, - 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xc9, 0x0f, 0x0a, - 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, - 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, - 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, - 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, - 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x67, 0x0a, 0x13, 0x61, + 0x70, 0x70, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x11, 0x61, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x63, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, + 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, + 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, + 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, + 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x22, + 0xb6, 0x02, 0x0a, 0x0b, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x4c, 0x0a, 0x0c, 0x68, 0x74, 0x74, + 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x2e, 0x48, 0x74, 0x74, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, + 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x1a, 0x5a, 0x0a, 0x0b, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x65, 0x64, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x52, 0x75, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, + 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x6e, + 0x65, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x50, + 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe9, 0x01, 0x0a, 0x16, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x4b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, + 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, + 0x22, 0x42, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x4f, 0x4d, + 0x50, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x10, 0x03, 0x22, 0x19, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x8f, 0x05, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x01, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x88, 0x01, + 0x01, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x50, 0x0a, 0x10, 0x69, 0x6e, 0x73, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x46, 0x69, 0x6c, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, + 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, + 0x6b, 0x69, 0x6c, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x6b, 0x69, 0x6c, + 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x05, 0x73, 0x6b, 0x69, + 0x6c, 0x6c, 0x12, 0x3e, 0x0a, 0x0a, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x43, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0a, 0x6d, 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x22, 0x61, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, + 0x4f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x5a, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, + 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x43, 0x4c, 0x55, + 0x44, 0x45, 0x44, 0x10, 0x05, 0x42, 0x06, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x0e, 0x0a, + 0x0c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x4a, 0x04, 0x08, + 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, + 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x4a, 0x04, 0x08, 0x10, 0x10, + 0x11, 0x22, 0x2f, 0x0a, 0x13, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x46, 0x69, 0x6c, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x0d, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x42, + 0x6f, 0x64, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0f, 0x0a, + 0x0d, 0x4d, 0x43, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x81, + 0x01, 0x0a, 0x0d, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x6f, 0x64, 0x79, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x05, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x43, 0x50, 0x54, 0x6f, 0x6f, 0x6c, 0x52, 0x05, 0x74, 0x6f, 0x6f, + 0x6c, 0x73, 0x22, 0x7b, 0x0a, 0x07, 0x4d, 0x43, 0x50, 0x54, 0x6f, 0x6f, 0x6c, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x52, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, + 0xe0, 0x01, 0x0a, 0x17, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, + 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3d, 0x0a, 0x09, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4a, 0x04, 0x08, 0x05, + 0x10, 0x06, 0x22, 0x36, 0x0a, 0x18, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, + 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, + 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, + 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, + 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, + 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, + 0xc9, 0x0f, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, + 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, + 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, + 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, + 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, + 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, - 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, - 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, - 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, - 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x64, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, - 0x01, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x89, 0x01, 0x0a, 0x1c, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x12, 0x5f, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6b, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, - 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6f, - 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, - 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, - 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x65, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, + 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x7e, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, + 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x5f, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, + 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, + 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6f, 0x75, + 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6f, 0x75, 0x6e, + 0x64, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x62, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -6481,7 +6524,7 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte { } var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 16) -var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 74) +var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 75) var file_agent_proto_agent_proto_goTypes = []interface{}{ (AppHealth)(0), // 0: coder.agent.v2.AppHealth (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel @@ -6561,33 +6604,34 @@ var file_agent_proto_agent_proto_goTypes = []interface{}{ nil, // 75: coder.agent.v2.Manifest.EnvironmentVariablesEntry nil, // 76: coder.agent.v2.Stats.ConnectionsByProtoEntry (*Stats_Metric)(nil), // 77: coder.agent.v2.Stats.Metric - (*Stats_Metric_Label)(nil), // 78: coder.agent.v2.Stats.Metric.Label - (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 79: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 80: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config - (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 81: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory - (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 82: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume - (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 83: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint - (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 84: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage - (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 85: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage - (*CreateSubAgentRequest_App)(nil), // 86: coder.agent.v2.CreateSubAgentRequest.App - (*CreateSubAgentRequest_App_Healthcheck)(nil), // 87: coder.agent.v2.CreateSubAgentRequest.App.Healthcheck - (*CreateSubAgentResponse_AppCreationError)(nil), // 88: coder.agent.v2.CreateSubAgentResponse.AppCreationError - (*BoundaryLog_HttpRequest)(nil), // 89: coder.agent.v2.BoundaryLog.HttpRequest - (*durationpb.Duration)(nil), // 90: google.protobuf.Duration - (*proto.DERPMap)(nil), // 91: coder.tailnet.v2.DERPMap - (*timestamppb.Timestamp)(nil), // 92: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 93: google.protobuf.Struct - (*emptypb.Empty)(nil), // 94: google.protobuf.Empty + nil, // 78: coder.agent.v2.Stats.SessionCountsEntry + (*Stats_Metric_Label)(nil), // 79: coder.agent.v2.Stats.Metric.Label + (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 80: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 81: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 82: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 83: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 84: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 85: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 86: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + (*CreateSubAgentRequest_App)(nil), // 87: coder.agent.v2.CreateSubAgentRequest.App + (*CreateSubAgentRequest_App_Healthcheck)(nil), // 88: coder.agent.v2.CreateSubAgentRequest.App.Healthcheck + (*CreateSubAgentResponse_AppCreationError)(nil), // 89: coder.agent.v2.CreateSubAgentResponse.AppCreationError + (*BoundaryLog_HttpRequest)(nil), // 90: coder.agent.v2.BoundaryLog.HttpRequest + (*durationpb.Duration)(nil), // 91: google.protobuf.Duration + (*proto.DERPMap)(nil), // 92: coder.tailnet.v2.DERPMap + (*timestamppb.Timestamp)(nil), // 93: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 94: google.protobuf.Struct + (*emptypb.Empty)(nil), // 95: google.protobuf.Empty } var file_agent_proto_agent_proto_depIdxs = []int32{ 1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel 72, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck 2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health - 90, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration + 91, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration 73, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result 74, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description 75, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry - 91, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap + 92, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap 17, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript 16, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp 74, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description @@ -6595,106 +6639,107 @@ var file_agent_proto_agent_proto_depIdxs = []int32{ 20, // 12: coder.agent.v2.Manifest.secrets:type_name -> coder.agent.v2.WorkspaceSecret 76, // 13: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry 77, // 14: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric - 25, // 15: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats - 90, // 16: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration - 4, // 17: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State - 92, // 18: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp - 28, // 19: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle - 79, // 20: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - 5, // 21: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem - 32, // 22: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup - 73, // 23: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result - 34, // 24: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata - 92, // 25: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp - 6, // 26: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level - 37, // 27: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log - 42, // 28: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig - 45, // 29: coder.agent.v2.WorkspaceAgentScriptCompletedRequest.timing:type_name -> coder.agent.v2.Timing - 92, // 30: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp - 92, // 31: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp - 7, // 32: coder.agent.v2.Timing.stage:type_name -> coder.agent.v2.Timing.Stage - 8, // 33: coder.agent.v2.Timing.status:type_name -> coder.agent.v2.Timing.Status - 80, // 34: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config - 81, // 35: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory - 82, // 36: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume - 83, // 37: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint - 9, // 38: coder.agent.v2.Connection.action:type_name -> coder.agent.v2.Connection.Action - 10, // 39: coder.agent.v2.Connection.type:type_name -> coder.agent.v2.Connection.Type - 92, // 40: coder.agent.v2.Connection.timestamp:type_name -> google.protobuf.Timestamp - 50, // 41: coder.agent.v2.ReportConnectionRequest.connection:type_name -> coder.agent.v2.Connection - 86, // 42: coder.agent.v2.CreateSubAgentRequest.apps:type_name -> coder.agent.v2.CreateSubAgentRequest.App - 11, // 43: coder.agent.v2.CreateSubAgentRequest.display_apps:type_name -> coder.agent.v2.CreateSubAgentRequest.DisplayApp - 52, // 44: coder.agent.v2.CreateSubAgentResponse.agent:type_name -> coder.agent.v2.SubAgent - 88, // 45: coder.agent.v2.CreateSubAgentResponse.app_creation_errors:type_name -> coder.agent.v2.CreateSubAgentResponse.AppCreationError - 52, // 46: coder.agent.v2.ListSubAgentsResponse.agents:type_name -> coder.agent.v2.SubAgent - 92, // 47: coder.agent.v2.BoundaryLog.time:type_name -> google.protobuf.Timestamp - 89, // 48: coder.agent.v2.BoundaryLog.http_request:type_name -> coder.agent.v2.BoundaryLog.HttpRequest - 59, // 49: coder.agent.v2.ReportBoundaryLogsRequest.logs:type_name -> coder.agent.v2.BoundaryLog - 14, // 50: coder.agent.v2.UpdateAppStatusRequest.state:type_name -> coder.agent.v2.UpdateAppStatusRequest.AppStatusState - 15, // 51: coder.agent.v2.ContextResource.status:type_name -> coder.agent.v2.ContextResource.Status - 65, // 52: coder.agent.v2.ContextResource.instruction_file:type_name -> coder.agent.v2.InstructionFileBody - 66, // 53: coder.agent.v2.ContextResource.skill:type_name -> coder.agent.v2.SkillMetaBody - 67, // 54: coder.agent.v2.ContextResource.mcp_config:type_name -> coder.agent.v2.MCPConfigBody - 68, // 55: coder.agent.v2.ContextResource.mcp_server:type_name -> coder.agent.v2.MCPServerBody - 69, // 56: coder.agent.v2.MCPServerBody.tools:type_name -> coder.agent.v2.MCPTool - 93, // 57: coder.agent.v2.MCPTool.input_schema:type_name -> google.protobuf.Struct - 64, // 58: coder.agent.v2.PushContextStateRequest.resources:type_name -> coder.agent.v2.ContextResource - 90, // 59: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration - 92, // 60: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp - 90, // 61: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration - 90, // 62: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration - 3, // 63: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type - 78, // 64: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label - 0, // 65: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth - 92, // 66: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp - 84, // 67: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage - 85, // 68: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage - 87, // 69: coder.agent.v2.CreateSubAgentRequest.App.healthcheck:type_name -> coder.agent.v2.CreateSubAgentRequest.App.Healthcheck - 12, // 70: coder.agent.v2.CreateSubAgentRequest.App.open_in:type_name -> coder.agent.v2.CreateSubAgentRequest.App.OpenIn - 13, // 71: coder.agent.v2.CreateSubAgentRequest.App.share:type_name -> coder.agent.v2.CreateSubAgentRequest.App.SharingLevel - 22, // 72: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest - 24, // 73: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest - 26, // 74: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest - 29, // 75: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest - 30, // 76: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest - 33, // 77: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest - 35, // 78: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest - 38, // 79: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest - 40, // 80: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest - 43, // 81: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest - 46, // 82: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest - 48, // 83: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest - 51, // 84: coder.agent.v2.Agent.ReportConnection:input_type -> coder.agent.v2.ReportConnectionRequest - 53, // 85: coder.agent.v2.Agent.CreateSubAgent:input_type -> coder.agent.v2.CreateSubAgentRequest - 55, // 86: coder.agent.v2.Agent.DeleteSubAgent:input_type -> coder.agent.v2.DeleteSubAgentRequest - 57, // 87: coder.agent.v2.Agent.ListSubAgents:input_type -> coder.agent.v2.ListSubAgentsRequest - 60, // 88: coder.agent.v2.Agent.ReportBoundaryLogs:input_type -> coder.agent.v2.ReportBoundaryLogsRequest - 62, // 89: coder.agent.v2.Agent.UpdateAppStatus:input_type -> coder.agent.v2.UpdateAppStatusRequest - 70, // 90: coder.agent.v2.Agent.PushContextState:input_type -> coder.agent.v2.PushContextStateRequest - 19, // 91: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest - 23, // 92: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner - 27, // 93: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse - 28, // 94: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle - 31, // 95: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse - 32, // 96: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup - 36, // 97: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse - 39, // 98: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse - 41, // 99: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse - 44, // 100: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse - 47, // 101: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse - 49, // 102: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse - 94, // 103: coder.agent.v2.Agent.ReportConnection:output_type -> google.protobuf.Empty - 54, // 104: coder.agent.v2.Agent.CreateSubAgent:output_type -> coder.agent.v2.CreateSubAgentResponse - 56, // 105: coder.agent.v2.Agent.DeleteSubAgent:output_type -> coder.agent.v2.DeleteSubAgentResponse - 58, // 106: coder.agent.v2.Agent.ListSubAgents:output_type -> coder.agent.v2.ListSubAgentsResponse - 61, // 107: coder.agent.v2.Agent.ReportBoundaryLogs:output_type -> coder.agent.v2.ReportBoundaryLogsResponse - 63, // 108: coder.agent.v2.Agent.UpdateAppStatus:output_type -> coder.agent.v2.UpdateAppStatusResponse - 71, // 109: coder.agent.v2.Agent.PushContextState:output_type -> coder.agent.v2.PushContextStateResponse - 91, // [91:110] is the sub-list for method output_type - 72, // [72:91] is the sub-list for method input_type - 72, // [72:72] is the sub-list for extension type_name - 72, // [72:72] is the sub-list for extension extendee - 0, // [0:72] is the sub-list for field type_name + 78, // 15: coder.agent.v2.Stats.session_counts:type_name -> coder.agent.v2.Stats.SessionCountsEntry + 25, // 16: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats + 91, // 17: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration + 4, // 18: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State + 93, // 19: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp + 28, // 20: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle + 80, // 21: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + 5, // 22: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem + 32, // 23: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup + 73, // 24: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 34, // 25: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata + 93, // 26: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp + 6, // 27: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level + 37, // 28: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log + 42, // 29: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig + 45, // 30: coder.agent.v2.WorkspaceAgentScriptCompletedRequest.timing:type_name -> coder.agent.v2.Timing + 93, // 31: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp + 93, // 32: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp + 7, // 33: coder.agent.v2.Timing.stage:type_name -> coder.agent.v2.Timing.Stage + 8, // 34: coder.agent.v2.Timing.status:type_name -> coder.agent.v2.Timing.Status + 81, // 35: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + 82, // 36: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + 83, // 37: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + 84, // 38: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + 9, // 39: coder.agent.v2.Connection.action:type_name -> coder.agent.v2.Connection.Action + 10, // 40: coder.agent.v2.Connection.type:type_name -> coder.agent.v2.Connection.Type + 93, // 41: coder.agent.v2.Connection.timestamp:type_name -> google.protobuf.Timestamp + 50, // 42: coder.agent.v2.ReportConnectionRequest.connection:type_name -> coder.agent.v2.Connection + 87, // 43: coder.agent.v2.CreateSubAgentRequest.apps:type_name -> coder.agent.v2.CreateSubAgentRequest.App + 11, // 44: coder.agent.v2.CreateSubAgentRequest.display_apps:type_name -> coder.agent.v2.CreateSubAgentRequest.DisplayApp + 52, // 45: coder.agent.v2.CreateSubAgentResponse.agent:type_name -> coder.agent.v2.SubAgent + 89, // 46: coder.agent.v2.CreateSubAgentResponse.app_creation_errors:type_name -> coder.agent.v2.CreateSubAgentResponse.AppCreationError + 52, // 47: coder.agent.v2.ListSubAgentsResponse.agents:type_name -> coder.agent.v2.SubAgent + 93, // 48: coder.agent.v2.BoundaryLog.time:type_name -> google.protobuf.Timestamp + 90, // 49: coder.agent.v2.BoundaryLog.http_request:type_name -> coder.agent.v2.BoundaryLog.HttpRequest + 59, // 50: coder.agent.v2.ReportBoundaryLogsRequest.logs:type_name -> coder.agent.v2.BoundaryLog + 14, // 51: coder.agent.v2.UpdateAppStatusRequest.state:type_name -> coder.agent.v2.UpdateAppStatusRequest.AppStatusState + 15, // 52: coder.agent.v2.ContextResource.status:type_name -> coder.agent.v2.ContextResource.Status + 65, // 53: coder.agent.v2.ContextResource.instruction_file:type_name -> coder.agent.v2.InstructionFileBody + 66, // 54: coder.agent.v2.ContextResource.skill:type_name -> coder.agent.v2.SkillMetaBody + 67, // 55: coder.agent.v2.ContextResource.mcp_config:type_name -> coder.agent.v2.MCPConfigBody + 68, // 56: coder.agent.v2.ContextResource.mcp_server:type_name -> coder.agent.v2.MCPServerBody + 69, // 57: coder.agent.v2.MCPServerBody.tools:type_name -> coder.agent.v2.MCPTool + 94, // 58: coder.agent.v2.MCPTool.input_schema:type_name -> google.protobuf.Struct + 64, // 59: coder.agent.v2.PushContextStateRequest.resources:type_name -> coder.agent.v2.ContextResource + 91, // 60: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration + 93, // 61: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp + 91, // 62: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration + 91, // 63: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration + 3, // 64: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type + 79, // 65: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label + 0, // 66: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth + 93, // 67: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp + 85, // 68: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + 86, // 69: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + 88, // 70: coder.agent.v2.CreateSubAgentRequest.App.healthcheck:type_name -> coder.agent.v2.CreateSubAgentRequest.App.Healthcheck + 12, // 71: coder.agent.v2.CreateSubAgentRequest.App.open_in:type_name -> coder.agent.v2.CreateSubAgentRequest.App.OpenIn + 13, // 72: coder.agent.v2.CreateSubAgentRequest.App.share:type_name -> coder.agent.v2.CreateSubAgentRequest.App.SharingLevel + 22, // 73: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest + 24, // 74: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest + 26, // 75: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest + 29, // 76: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest + 30, // 77: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest + 33, // 78: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest + 35, // 79: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest + 38, // 80: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest + 40, // 81: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest + 43, // 82: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest + 46, // 83: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest + 48, // 84: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest + 51, // 85: coder.agent.v2.Agent.ReportConnection:input_type -> coder.agent.v2.ReportConnectionRequest + 53, // 86: coder.agent.v2.Agent.CreateSubAgent:input_type -> coder.agent.v2.CreateSubAgentRequest + 55, // 87: coder.agent.v2.Agent.DeleteSubAgent:input_type -> coder.agent.v2.DeleteSubAgentRequest + 57, // 88: coder.agent.v2.Agent.ListSubAgents:input_type -> coder.agent.v2.ListSubAgentsRequest + 60, // 89: coder.agent.v2.Agent.ReportBoundaryLogs:input_type -> coder.agent.v2.ReportBoundaryLogsRequest + 62, // 90: coder.agent.v2.Agent.UpdateAppStatus:input_type -> coder.agent.v2.UpdateAppStatusRequest + 70, // 91: coder.agent.v2.Agent.PushContextState:input_type -> coder.agent.v2.PushContextStateRequest + 19, // 92: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest + 23, // 93: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner + 27, // 94: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse + 28, // 95: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle + 31, // 96: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse + 32, // 97: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup + 36, // 98: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse + 39, // 99: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse + 41, // 100: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse + 44, // 101: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse + 47, // 102: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse + 49, // 103: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse + 95, // 104: coder.agent.v2.Agent.ReportConnection:output_type -> google.protobuf.Empty + 54, // 105: coder.agent.v2.Agent.CreateSubAgent:output_type -> coder.agent.v2.CreateSubAgentResponse + 56, // 106: coder.agent.v2.Agent.DeleteSubAgent:output_type -> coder.agent.v2.DeleteSubAgentResponse + 58, // 107: coder.agent.v2.Agent.ListSubAgents:output_type -> coder.agent.v2.ListSubAgentsResponse + 61, // 108: coder.agent.v2.Agent.ReportBoundaryLogs:output_type -> coder.agent.v2.ReportBoundaryLogsResponse + 63, // 109: coder.agent.v2.Agent.UpdateAppStatus:output_type -> coder.agent.v2.UpdateAppStatusResponse + 71, // 110: coder.agent.v2.Agent.PushContextState:output_type -> coder.agent.v2.PushContextStateResponse + 92, // [92:111] is the sub-list for method output_type + 73, // [73:92] is the sub-list for method input_type + 73, // [73:73] is the sub-list for extension type_name + 73, // [73:73] is the sub-list for extension extendee + 0, // [0:73] is the sub-list for field type_name } func init() { file_agent_proto_agent_proto_init() } @@ -7423,7 +7468,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stats_Metric_Label); i { case 0: return &v.state @@ -7435,7 +7480,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i { case 0: return &v.state @@ -7447,7 +7492,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Config); i { case 0: return &v.state @@ -7459,7 +7504,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Memory); i { case 0: return &v.state @@ -7471,7 +7516,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Volume); i { case 0: return &v.state @@ -7483,7 +7528,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint); i { case 0: return &v.state @@ -7495,7 +7540,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage); i { case 0: return &v.state @@ -7507,7 +7552,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage); i { case 0: return &v.state @@ -7519,7 +7564,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentRequest_App); i { case 0: return &v.state @@ -7531,7 +7576,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentRequest_App_Healthcheck); i { case 0: return &v.state @@ -7543,7 +7588,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentResponse_AppCreationError); i { case 0: return &v.state @@ -7555,7 +7600,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BoundaryLog_HttpRequest); i { case 0: return &v.state @@ -7582,16 +7627,16 @@ func file_agent_proto_agent_proto_init() { (*ContextResource_McpConfig)(nil), (*ContextResource_McpServer)(nil), } - file_agent_proto_agent_proto_msgTypes[67].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[70].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[72].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[68].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[71].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[73].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_agent_proto_agent_proto_rawDesc, NumEnums: 16, - NumMessages: 74, + NumMessages: 75, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/proto/agent.proto b/agent/proto/agent.proto index f11c9a0f28d..1dbf0cb9879 100644 --- a/agent/proto/agent.proto +++ b/agent/proto/agent.proto @@ -152,16 +152,25 @@ message Stats { // SessionCountVSCode is the number of connections received by an agent // that are from our VS Code extension. - int64 session_count_vscode = 8; + // + // Deprecated: Use session_counts instead. Only set by old agents; the + // server converts these fields into the map when the map is empty. + int64 session_count_vscode = 8 [deprecated = true]; // SessionCountJetBrains is the number of connections received by an agent // that are from our JetBrains extension. - int64 session_count_jetbrains = 9; + // + // Deprecated: Use session_counts instead. + int64 session_count_jetbrains = 9 [deprecated = true]; // SessionCountReconnectingPTY is the number of connections received by an agent // that are from the reconnecting web terminal. - int64 session_count_reconnecting_pty = 10; + // + // Deprecated: Use session_counts instead. + int64 session_count_reconnecting_pty = 10 [deprecated = true]; // SessionCountSSH is the number of connections received by an agent // that are normal, non-tagged SSH sessions. - int64 session_count_ssh = 11; + // + // Deprecated: Use session_counts instead. + int64 session_count_ssh = 11 [deprecated = true]; message Metric { string name = 1; @@ -182,6 +191,12 @@ message Stats { repeated Label labels = 4; } repeated Metric metrics = 12; + + // SessionCounts is the number of sessions per app, keyed by the app + // identifier reported by the client, normalized at ingestion (e.g. + // "vscode", "jetbrains", "ssh", "reconnecting_pty"). Replaces the + // deprecated session_count_* fields. + map session_counts = 13; } message UpdateStatsRequest{ diff --git a/agent/proto/agent_drpc_old.go b/agent/proto/agent_drpc_old.go index f83c52c01ec..886fef9552c 100644 --- a/agent/proto/agent_drpc_old.go +++ b/agent/proto/agent_drpc_old.go @@ -99,3 +99,10 @@ type DRPCAgentClient210 interface { DRPCAgentClient29 PushContextState(ctx context.Context, in *PushContextStateRequest) (*PushContextStateResponse, error) } + +// DRPCAgentClient211 is the Agent API at v2.11. It adds the +// session_counts map field to Stats, deprecating the fixed +// session_count_* fields. No new RPCs. +type DRPCAgentClient211 interface { + DRPCAgentClient210 +} diff --git a/cli/ssh.go b/cli/ssh.go index d18ac8909f5..007a5af2ea5 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -1532,17 +1532,12 @@ func getUsageAppName(usageApp string) codersdk.UsageAppName { if usageApp == disableUsageApp { return "" } - - allowedUsageApps := []string{ - string(codersdk.UsageAppNameSSH), - string(codersdk.UsageAppNameVscode), - string(codersdk.UsageAppNameJetbrains), - } - if slices.Contains(allowedUsageApps, usageApp) { - return codersdk.UsageAppName(usageApp) + if usageApp == "" { + return codersdk.UsageAppNameSSH } - return codersdk.UsageAppNameSSH + // The server accepts arbitrary app names. + return codersdk.UsageAppName(usageApp) } func setStatsCallback( diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 2221a23e7bf..b4b5d1387a5 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -1630,51 +1630,51 @@ func TestSSH(t *testing.T) { t.Parallel() type testCase struct { - name string - experiment bool - usageAppName string - expectedCalls int - expectedCountSSH int - expectedCountJetbrains int - expectedCountVscode int + name string + experiment bool + usageAppName string + expectedCalls int + expectedCounts map[string]int64 } tcs := []testCase{ { name: "NoExperiment", }, { - name: "Empty", - experiment: true, - expectedCalls: 1, - expectedCountSSH: 1, + name: "Empty", + experiment: true, + expectedCalls: 1, + expectedCounts: map[string]int64{"ssh": 1}, }, { - name: "SSH", - experiment: true, - usageAppName: "ssh", - expectedCalls: 1, - expectedCountSSH: 1, + name: "SSH", + experiment: true, + usageAppName: "ssh", + expectedCalls: 1, + expectedCounts: map[string]int64{"ssh": 1}, }, { - name: "Jetbrains", - experiment: true, - usageAppName: "jetbrains", - expectedCalls: 1, - expectedCountJetbrains: 1, + name: "Jetbrains", + experiment: true, + usageAppName: "jetbrains", + expectedCalls: 1, + expectedCounts: map[string]int64{"jetbrains": 1}, }, { - name: "Vscode", - experiment: true, - usageAppName: "vscode", - expectedCalls: 1, - expectedCountVscode: 1, + name: "Vscode", + experiment: true, + usageAppName: "vscode", + expectedCalls: 1, + expectedCounts: map[string]int64{"vscode": 1}, }, { - name: "InvalidDefaultsToSSH", - experiment: true, - usageAppName: "invalid", - expectedCalls: 1, - expectedCountSSH: 1, + // Arbitrary app names pass through raw (normalized at + // ingestion), so new IDEs need no CLI changes. + name: "ArbitraryNamePassthrough", + experiment: true, + usageAppName: "SomeFutureIDE", + expectedCalls: 1, + expectedCounts: map[string]int64{"SomeFutureIDE": 1}, }, { name: "Disable", @@ -1730,9 +1730,11 @@ func TestSSH(t *testing.T) { <-cmdDone require.EqualValues(t, tc.expectedCalls, batcher.Called) - require.EqualValues(t, tc.expectedCountSSH, batcher.LastStats.SessionCountSsh) - require.EqualValues(t, tc.expectedCountJetbrains, batcher.LastStats.SessionCountJetbrains) - require.EqualValues(t, tc.expectedCountVscode, batcher.LastStats.SessionCountVscode) + if len(tc.expectedCounts) == 0 { + require.Empty(t, batcher.LastStats.GetSessionCounts()) + } else { + require.EqualValues(t, tc.expectedCounts, batcher.LastStats.GetSessionCounts()) + } }) } }) diff --git a/cli/vscodessh_test.go b/cli/vscodessh_test.go index 32afb52ca1d..beb08e9cc84 100644 --- a/cli/vscodessh_test.go +++ b/cli/vscodessh_test.go @@ -87,5 +87,5 @@ func TestVSCodeSSH(t *testing.T) { } require.EqualValues(t, 1, batcher.Called) - require.EqualValues(t, 1, batcher.LastStats.SessionCountVscode) + require.EqualValues(t, 1, batcher.LastStats.GetSessionCounts()["vscode"]) } diff --git a/coderd/agentapi/stats.go b/coderd/agentapi/stats.go index d6a698b5508..d5a63ac6c53 100644 --- a/coderd/agentapi/stats.go +++ b/coderd/agentapi/stats.go @@ -66,10 +66,7 @@ func (a *StatsAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsR // while the experiment is enabled we will not report // session stats from the agent. This is because it is // being handled by the CLI and the postWorkspaceUsage route. - req.Stats.SessionCountSsh = 0 - req.Stats.SessionCountJetbrains = 0 - req.Stats.SessionCountVscode = 0 - req.Stats.SessionCountReconnectingPty = 0 + workspacestats.ClearSessionCounts(req.Stats) } err := a.StatsReporter.ReportAgentStats( diff --git a/coderd/agentapi/stats_test.go b/coderd/agentapi/stats_test.go index bf6c41e550c..6a027e5f6cd 100644 --- a/coderd/agentapi/stats_test.go +++ b/coderd/agentapi/stats_test.go @@ -525,6 +525,8 @@ func TestUpdateStats(t *testing.T) { batcher.Mu.Lock() defer batcher.Mu.Unlock() require.EqualValues(t, 1, batcher.Called) + require.Empty(t, batcher.LastStats.GetSessionCounts()) + //nolint:staticcheck // Deprecated fields must also be cleared for old-agent reports. require.EqualValues(t, 0, batcher.LastStats.SessionCountSsh) require.EqualValues(t, 0, batcher.LastStats.SessionCountJetbrains) require.EqualValues(t, 0, batcher.LastStats.SessionCountVscode) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 10cfe4dddff..c4a8193a44e 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -1673,54 +1673,57 @@ func TemplateVersionTerraformValues(t testing.TB, db database.Store, orig databa return v } -func WorkspaceAgentStat(t testing.TB, db database.Store, orig database.WorkspaceAgentStat) database.WorkspaceAgentStat { +// WorkspaceAgentStat inserts a workspace agent stat row. The optional map seeds +// its session_counts column. +func WorkspaceAgentStat(t testing.TB, db database.Store, orig database.WorkspaceAgentStat, sessionCounts ...map[string]int64) database.WorkspaceAgentStat { if orig.ConnectionsByProto == nil { orig.ConnectionsByProto = json.RawMessage([]byte("{}")) } jsonProto := []byte(fmt.Sprintf("[%s]", orig.ConnectionsByProto)) + // The insert rejects null session count elements. + counts := map[string]int64{} + if len(sessionCounts) > 0 && sessionCounts[0] != nil { + counts = sessionCounts[0] + } + jsonSessionCounts, err := json.Marshal([]map[string]int64{counts}) + require.NoError(t, err, "marshal session counts") + params := database.InsertWorkspaceAgentStatsParams{ - ID: []uuid.UUID{takeFirst(orig.ID, uuid.New())}, - CreatedAt: []time.Time{takeFirst(orig.CreatedAt, dbtime.Now())}, - UserID: []uuid.UUID{takeFirst(orig.UserID, uuid.New())}, - TemplateID: []uuid.UUID{takeFirst(orig.TemplateID, uuid.New())}, - WorkspaceID: []uuid.UUID{takeFirst(orig.WorkspaceID, uuid.New())}, - AgentID: []uuid.UUID{takeFirst(orig.AgentID, uuid.New())}, - ConnectionsByProto: jsonProto, - ConnectionCount: []int64{takeFirst(orig.ConnectionCount, 0)}, - RxPackets: []int64{takeFirst(orig.RxPackets, 0)}, - RxBytes: []int64{takeFirst(orig.RxBytes, 0)}, - TxPackets: []int64{takeFirst(orig.TxPackets, 0)}, - TxBytes: []int64{takeFirst(orig.TxBytes, 0)}, - SessionCountVSCode: []int64{takeFirst(orig.SessionCountVSCode, 0)}, - SessionCountJetBrains: []int64{takeFirst(orig.SessionCountJetBrains, 0)}, - SessionCountReconnectingPTY: []int64{takeFirst(orig.SessionCountReconnectingPTY, 0)}, - SessionCountSSH: []int64{takeFirst(orig.SessionCountSSH, 0)}, - ConnectionMedianLatencyMS: []float64{takeFirst(orig.ConnectionMedianLatencyMS, 0)}, - Usage: []bool{takeFirst(orig.Usage, false)}, - } - err := db.InsertWorkspaceAgentStats(genCtx, params) + ID: []uuid.UUID{takeFirst(orig.ID, uuid.New())}, + CreatedAt: []time.Time{takeFirst(orig.CreatedAt, dbtime.Now())}, + UserID: []uuid.UUID{takeFirst(orig.UserID, uuid.New())}, + TemplateID: []uuid.UUID{takeFirst(orig.TemplateID, uuid.New())}, + WorkspaceID: []uuid.UUID{takeFirst(orig.WorkspaceID, uuid.New())}, + AgentID: []uuid.UUID{takeFirst(orig.AgentID, uuid.New())}, + ConnectionsByProto: jsonProto, + ConnectionCount: []int64{takeFirst(orig.ConnectionCount, 0)}, + RxPackets: []int64{takeFirst(orig.RxPackets, 0)}, + RxBytes: []int64{takeFirst(orig.RxBytes, 0)}, + TxPackets: []int64{takeFirst(orig.TxPackets, 0)}, + TxBytes: []int64{takeFirst(orig.TxBytes, 0)}, + SessionCounts: jsonSessionCounts, + ConnectionMedianLatencyMS: []float64{takeFirst(orig.ConnectionMedianLatencyMS, 0)}, + Usage: []bool{takeFirst(orig.Usage, false)}, + } + err = db.InsertWorkspaceAgentStats(genCtx, params) require.NoError(t, err, "insert workspace agent stat") return database.WorkspaceAgentStat{ - ID: params.ID[0], - CreatedAt: params.CreatedAt[0], - UserID: params.UserID[0], - AgentID: params.AgentID[0], - WorkspaceID: params.WorkspaceID[0], - TemplateID: params.TemplateID[0], - ConnectionsByProto: orig.ConnectionsByProto, - ConnectionCount: params.ConnectionCount[0], - RxPackets: params.RxPackets[0], - RxBytes: params.RxBytes[0], - TxPackets: params.TxPackets[0], - TxBytes: params.TxBytes[0], - ConnectionMedianLatencyMS: params.ConnectionMedianLatencyMS[0], - SessionCountVSCode: params.SessionCountVSCode[0], - SessionCountJetBrains: params.SessionCountJetBrains[0], - SessionCountReconnectingPTY: params.SessionCountReconnectingPTY[0], - SessionCountSSH: params.SessionCountSSH[0], - Usage: params.Usage[0], + ID: params.ID[0], + CreatedAt: params.CreatedAt[0], + UserID: params.UserID[0], + AgentID: params.AgentID[0], + WorkspaceID: params.WorkspaceID[0], + TemplateID: params.TemplateID[0], + ConnectionsByProto: orig.ConnectionsByProto, + ConnectionCount: params.ConnectionCount[0], + RxPackets: params.RxPackets[0], + RxBytes: params.RxBytes[0], + TxPackets: params.TxPackets[0], + TxBytes: params.TxBytes[0], + ConnectionMedianLatencyMS: params.ConnectionMedianLatencyMS[0], + Usage: params.Usage[0], } } diff --git a/coderd/database/dbpurge/dbpurge_test.go b/coderd/database/dbpurge/dbpurge_test.go index 242f9c33324..c41023cb62c 100644 --- a/coderd/database/dbpurge/dbpurge_test.go +++ b/coderd/database/dbpurge/dbpurge_test.go @@ -387,8 +387,7 @@ func TestDeleteOldWorkspaceAgentStats(t *testing.T) { ConnectionCount: 1, ConnectionMedianLatencyMS: 1, RxBytes: 1111, - SessionCountSSH: 1, - }) + }, map[string]int64{"ssh": 1}) // Stat inserted 180 days - 2 hour ago, should not be deleted before rollup. second := dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -396,8 +395,7 @@ func TestDeleteOldWorkspaceAgentStats(t *testing.T) { ConnectionCount: 1, ConnectionMedianLatencyMS: 1, RxBytes: 2222, - SessionCountSSH: 1, - }) + }, map[string]int64{"ssh": 1}) // Stat inserted 179 days - 4 hour ago, should not be deleted at all. third := dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -405,8 +403,7 @@ func TestDeleteOldWorkspaceAgentStats(t *testing.T) { ConnectionCount: 1, ConnectionMedianLatencyMS: 1, RxBytes: 3333, - SessionCountSSH: 1, - }) + }, map[string]int64{"ssh": 1}) // when closer := dbpurge.New(ctx, logger, db, &codersdk.DeploymentValues{}, prometheus.NewRegistry(), dbpurge.WithClock(clk)) diff --git a/coderd/database/dbrollup/dbrollup_test.go b/coderd/database/dbrollup/dbrollup_test.go index ebcb6852a3f..4be76c89279 100644 --- a/coderd/database/dbrollup/dbrollup_test.go +++ b/coderd/database/dbrollup/dbrollup_test.go @@ -75,8 +75,7 @@ func TestRollup_TwoInstancesUseLocking(t *testing.T) { CreatedAt: refTime.Add(-time.Minute), ConnectionMedianLatencyMS: 1, ConnectionCount: 1, - SessionCountSSH: 1, - }) + }, map[string]int64{"ssh": 1}) closeRolluper := func(rolluper *dbrollup.Rolluper, resume chan struct{}) { close(resume) @@ -163,8 +162,7 @@ func TestRollupTemplateUsageStats(t *testing.T) { CreatedAt: anHourAndSixMonthsAgo.AddDate(0, 0, -1), ConnectionMedianLatencyMS: 1, ConnectionCount: 1, - SessionCountSSH: 1, - }) + }, map[string]int64{"ssh": 1}) _ = dbgen.WorkspaceAppStat(t, db, database.WorkspaceAppStat{ UserID: user.ID, WorkspaceID: ws.ID, @@ -176,25 +174,23 @@ func TestRollupTemplateUsageStats(t *testing.T) { // Stats inserted 6 months - 1 day ago, should be rolled up. wags1 := dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - TemplateID: tpl.ID, - WorkspaceID: ws.ID, - AgentID: agent.ID, - UserID: user.ID, - CreatedAt: anHourAndSixMonthsAgo.AddDate(0, 0, 1), - ConnectionMedianLatencyMS: 1, - ConnectionCount: 1, - SessionCountReconnectingPTY: 1, - }) + TemplateID: tpl.ID, + WorkspaceID: ws.ID, + AgentID: agent.ID, + UserID: user.ID, + CreatedAt: anHourAndSixMonthsAgo.AddDate(0, 0, 1), + ConnectionMedianLatencyMS: 1, + ConnectionCount: 1, + }, map[string]int64{"reconnecting_pty": 1}) wags2 := dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - TemplateID: tpl.ID, - WorkspaceID: ws.ID, - AgentID: agent.ID, - UserID: user.ID, - CreatedAt: wags1.CreatedAt.Add(time.Minute), - ConnectionMedianLatencyMS: 1, - ConnectionCount: 1, - SessionCountReconnectingPTY: 1, - }) + TemplateID: tpl.ID, + WorkspaceID: ws.ID, + AgentID: agent.ID, + UserID: user.ID, + CreatedAt: wags1.CreatedAt.Add(time.Minute), + ConnectionMedianLatencyMS: 1, + ConnectionCount: 1, + }, map[string]int64{"reconnecting_pty": 1}) // wags2 and waps1 overlap, so total usage is 4 - 1. waps1 := dbgen.WorkspaceAppStat(t, db, database.WorkspaceAppStat{ UserID: user.ID, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 79e987cc084..65c610385b7 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -3851,13 +3851,12 @@ CREATE TABLE workspace_agent_stats ( tx_packets bigint DEFAULT 0 NOT NULL, tx_bytes bigint DEFAULT 0 NOT NULL, connection_median_latency_ms double precision DEFAULT '-1'::integer NOT NULL, - session_count_vscode bigint DEFAULT 0 NOT NULL, - session_count_jetbrains bigint DEFAULT 0 NOT NULL, - session_count_reconnecting_pty bigint DEFAULT 0 NOT NULL, - session_count_ssh bigint DEFAULT 0 NOT NULL, - usage boolean DEFAULT false NOT NULL + usage boolean DEFAULT false NOT NULL, + session_counts jsonb DEFAULT '{}'::jsonb NOT NULL ); +COMMENT ON COLUMN workspace_agent_stats.session_counts IS 'Positive session counts keyed by the canonical app name reported by the agent.'; + CREATE TABLE workspace_agent_volume_resource_monitors ( agent_id uuid NOT NULL, enabled boolean NOT NULL, @@ -4973,7 +4972,7 @@ COMMENT ON INDEX workspace_agent_scripts_workspace_agent_id_idx IS 'Foreign key CREATE INDEX workspace_agent_startup_logs_id_agent_id_idx ON workspace_agent_logs USING btree (agent_id, id); -CREATE INDEX workspace_agent_stats_template_id_created_at_user_id_idx ON workspace_agent_stats USING btree (template_id, created_at, user_id) INCLUDE (session_count_vscode, session_count_jetbrains, session_count_reconnecting_pty, session_count_ssh, connection_median_latency_ms) WHERE (connection_count > 0); +CREATE INDEX workspace_agent_stats_template_id_created_at_user_id_idx ON workspace_agent_stats USING btree (template_id, created_at, user_id) INCLUDE (connection_median_latency_ms) WHERE (connection_count > 0); COMMENT ON INDEX workspace_agent_stats_template_id_created_at_user_id_idx IS 'Support index for template insights endpoint to build interval reports faster.'; diff --git a/coderd/database/migrations/000569_workspace_agent_session_counts.down.sql b/coderd/database/migrations/000569_workspace_agent_session_counts.down.sql new file mode 100644 index 00000000000..ed2d8ed4500 --- /dev/null +++ b/coderd/database/migrations/000569_workspace_agent_session_counts.down.sql @@ -0,0 +1,22 @@ +ALTER TABLE workspace_agent_stats + ADD COLUMN session_count_vscode bigint DEFAULT 0 NOT NULL, + ADD COLUMN session_count_jetbrains bigint DEFAULT 0 NOT NULL, + ADD COLUMN session_count_reconnecting_pty bigint DEFAULT 0 NOT NULL, + ADD COLUMN session_count_ssh bigint DEFAULT 0 NOT NULL; + +-- Restore the four known session counts. Other keys are discarded. +UPDATE workspace_agent_stats +SET + session_count_vscode = COALESCE((session_counts ->> 'vscode')::bigint, 0), + session_count_jetbrains = COALESCE((session_counts ->> 'jetbrains')::bigint, 0), + session_count_reconnecting_pty = COALESCE((session_counts ->> 'reconnecting_pty')::bigint, 0), + session_count_ssh = COALESCE((session_counts ->> 'ssh')::bigint, 0); + +DROP INDEX workspace_agent_stats_template_id_created_at_user_id_idx; + +ALTER TABLE workspace_agent_stats + DROP COLUMN session_counts; + +CREATE INDEX workspace_agent_stats_template_id_created_at_user_id_idx ON workspace_agent_stats USING btree (template_id, created_at, user_id) INCLUDE (session_count_vscode, session_count_jetbrains, session_count_reconnecting_pty, session_count_ssh, connection_median_latency_ms) WHERE (connection_count > 0); + +COMMENT ON INDEX workspace_agent_stats_template_id_created_at_user_id_idx IS 'Support index for template insights endpoint to build interval reports faster.'; diff --git a/coderd/database/migrations/000569_workspace_agent_session_counts.up.sql b/coderd/database/migrations/000569_workspace_agent_session_counts.up.sql new file mode 100644 index 00000000000..f9e78f0e8b9 --- /dev/null +++ b/coderd/database/migrations/000569_workspace_agent_session_counts.up.sql @@ -0,0 +1,71 @@ +LOCK TABLE workspace_agent_stats IN ACCESS EXCLUSIVE MODE; + +DO $$ +DECLARE + latest_rollup_start timestamptz; + migration_cutoff timestamptz; +BEGIN + SELECT MAX(start_time) INTO latest_rollup_start FROM template_usage_stats; + migration_cutoff := COALESCE(latest_rollup_start - interval '1 day', statement_timestamp() - interval '180 days'); + + IF (latest_rollup_start IS NULL OR latest_rollup_start < statement_timestamp() - interval '24 hours') + AND EXISTS ( + SELECT 1 + FROM workspace_agent_stats + WHERE created_at >= migration_cutoff + AND ( + session_count_vscode > 0 + OR session_count_jetbrains > 0 + OR session_count_reconnecting_pty > 0 + OR session_count_ssh > 0 + ) + ) + THEN + RAISE EXCEPTION 'migration 000569 requires template usage stats rolled up within the last 24 hours; run the previous Coder version until template usage stats roll up, then retry the upgrade' + USING DETAIL = format( + 'Latest template_usage_stats.start_time: %s.', + COALESCE(latest_rollup_start::text, 'none') + ), + HINT = 'Check coderd logs for "failed to rollup data" if the timestamp does not advance.'; + END IF; +END +$$; + +ALTER TABLE workspace_agent_stats + ADD COLUMN session_counts jsonb DEFAULT '{}'::jsonb NOT NULL; + +COMMENT ON COLUMN workspace_agent_stats.session_counts IS 'Positive session counts keyed by the canonical app name reported by the agent.'; + +-- Convert the raw window still used by rollups and operational statistics. +-- Older usage is already in template usage rollups. The guard requires a recent +-- rollup when retained rows need conversion. +UPDATE workspace_agent_stats +SET session_counts = jsonb_strip_nulls(jsonb_build_object( + 'vscode', CASE WHEN session_count_vscode > 0 THEN session_count_vscode END, + 'jetbrains', CASE WHEN session_count_jetbrains > 0 THEN session_count_jetbrains END, + 'reconnecting_pty', CASE WHEN session_count_reconnecting_pty > 0 THEN session_count_reconnecting_pty END, + 'ssh', CASE WHEN session_count_ssh > 0 THEN session_count_ssh END +)) +WHERE created_at >= ( + SELECT COALESCE(MAX(start_time) - interval '1 day', statement_timestamp() - interval '180 days') + FROM template_usage_stats +) + AND ( + session_count_vscode > 0 + OR session_count_jetbrains > 0 + OR session_count_reconnecting_pty > 0 + OR session_count_ssh > 0 + ); + +-- Recreate the index without the dropped columns in its INCLUDE list. +DROP INDEX workspace_agent_stats_template_id_created_at_user_id_idx; + +ALTER TABLE workspace_agent_stats + DROP COLUMN session_count_vscode, + DROP COLUMN session_count_jetbrains, + DROP COLUMN session_count_reconnecting_pty, + DROP COLUMN session_count_ssh; + +CREATE INDEX workspace_agent_stats_template_id_created_at_user_id_idx ON workspace_agent_stats USING btree (template_id, created_at, user_id) INCLUDE (connection_median_latency_ms) WHERE (connection_count > 0); + +COMMENT ON INDEX workspace_agent_stats_template_id_created_at_user_id_idx IS 'Support index for template insights endpoint to build interval reports faster.'; diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index df0c7d14ea9..9673e3a8c04 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -3,6 +3,7 @@ package migrations_test import ( "context" "database/sql" + "errors" "fmt" "os" "path/filepath" @@ -2878,6 +2879,118 @@ func TestMigration000565OAuth2ClientTypeConstraint(t *testing.T) { } } +//nolint:tparallel,paralleltest // Subtests share one database with transaction-local fixtures. +func TestMigration000569WorkspaceAgentSessionCountsGuard(t *testing.T) { + t.Parallel() + + const ( + priorMigrationVersion = 568 + noStatsAgeHours = -1 + ) + + sqlDB := testSQLDB(t) + next, err := migrations.Stepper(sqlDB) + require.NoError(t, err) + for { + version, more, err := next() + require.NoError(t, err) + if !more { + t.Fatalf("migration %d not found", priorMigrationVersion) + } + if version == priorMigrationVersion { + break + } + } + + ctx := testutil.Context(t, testutil.WaitSuperLong) + migrationSQL, err := os.ReadFile("000569_workspace_agent_session_counts.up.sql") + require.NoError(t, err) + + tests := []struct { + name string + statsAgeHours int + sessionCount int + watermarkAgeHours []int + wantError bool + wantCounts string + }{ + {name: "empty database", statsAgeHours: noStatsAgeHours}, + {name: "missing watermark with retained activity", sessionCount: 2, wantError: true}, + {name: "missing watermark with retained idle stats", wantCounts: `{}`}, + {name: "missing watermark with expired activity", statsAgeHours: 181 * 24, sessionCount: 2, wantCounts: `{}`}, + {name: "stale watermark with retained activity", sessionCount: 2, watermarkAgeHours: []int{25}, wantError: true}, + {name: "stale watermark with rolled up activity", statsAgeHours: 50, sessionCount: 2, watermarkAgeHours: []int{25}, wantCounts: `{}`}, + {name: "fresh watermark", sessionCount: 2, watermarkAgeHours: []int{23}, wantCounts: `{"vscode": 2}`}, + {name: "latest watermark is fresh", sessionCount: 2, watermarkAgeHours: []int{25, 23}, wantCounts: `{"vscode": 2}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tx, err := sqlDB.BeginTx(ctx, nil) + require.NoError(t, err) + t.Cleanup(func() { _ = tx.Rollback() }) + + if tt.statsAgeHours != noStatsAgeHours { + _, err = tx.ExecContext(ctx, ` + INSERT INTO workspace_agent_stats ( + id, created_at, user_id, agent_id, workspace_id, template_id, + connection_count, session_count_vscode + ) VALUES ( + gen_random_uuid(), statement_timestamp() - $1::bigint * interval '1 hour', + gen_random_uuid(), gen_random_uuid(), gen_random_uuid(), gen_random_uuid(), 1, $2 + ) + `, tt.statsAgeHours, tt.sessionCount) + require.NoError(t, err) + } + + for _, ageHours := range tt.watermarkAgeHours { + _, err = tx.ExecContext(ctx, ` + INSERT INTO template_usage_stats ( + start_time, end_time, template_id, user_id, median_latency_ms, + usage_mins, ssh_mins, sftp_mins, reconnecting_pty_mins, + vscode_mins, jetbrains_mins, app_usage_mins + ) VALUES ( + statement_timestamp() - $1::bigint * interval '1 hour', + statement_timestamp() - $1::bigint * interval '1 hour' + interval '30 minutes', + gen_random_uuid(), gen_random_uuid(), NULL, 0, 0, 0, 0, 0, 0, NULL + ) + `, ageHours) + require.NoError(t, err) + } + + _, err = tx.ExecContext(ctx, string(migrationSQL)) + if tt.wantError { + require.ErrorContains(t, err, "requires template usage stats rolled up within the last 24 hours") + var pqErr *pq.Error + require.True(t, errors.As(err, &pqErr)) + require.Contains(t, pqErr.Hint, "failed to rollup data") + require.NoError(t, tx.Rollback()) + + var columnExists bool + err = sqlDB.QueryRowContext(ctx, ` + SELECT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'workspace_agent_stats' + AND column_name = 'session_counts' + ) + `).Scan(&columnExists) + require.NoError(t, err) + require.False(t, columnExists) + return + } + require.NoError(t, err) + + if tt.wantCounts != "" { + var sessionCounts []byte + err = tx.QueryRowContext(ctx, `SELECT session_counts FROM workspace_agent_stats`).Scan(&sessionCounts) + require.NoError(t, err) + require.JSONEq(t, tt.wantCounts, string(sessionCounts)) + } + }) + } +} + // TestMigration000566OAuth2AuthMethodBackfill covers the repair the backfill // exists for, which the testdata/fixtures run does not reach: its only // oauth2_provider_apps row seeds no token_endpoint_auth_method at all, so the diff --git a/coderd/database/migrations/testdata/fixtures/000569_workspace_agent_session_counts.up.sql b/coderd/database/migrations/testdata/fixtures/000569_workspace_agent_session_counts.up.sql new file mode 100644 index 00000000000..d2eba9e9bd8 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000569_workspace_agent_session_counts.up.sql @@ -0,0 +1,8 @@ +INSERT INTO workspace_agent_stats ( + id, created_at, user_id, agent_id, workspace_id, template_id, + connection_count, connection_median_latency_ms, session_counts +) VALUES ( + '4a382ba5-6e57-4a58-991e-d4ac4f6c1012', NOW(), + gen_random_uuid(), gen_random_uuid(), gen_random_uuid(), gen_random_uuid(), + 1, 1, '{"vscode": 2, "some_future_ide": 1}'::jsonb +); diff --git a/coderd/database/models.go b/coderd/database/models.go index a68b7e54bc9..e5ccf6dff19 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -6507,24 +6507,22 @@ type WorkspaceAgentScriptTiming struct { } type WorkspaceAgentStat struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UserID uuid.UUID `db:"user_id" json:"user_id"` - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - ConnectionsByProto json.RawMessage `db:"connections_by_proto" json:"connections_by_proto"` - ConnectionCount int64 `db:"connection_count" json:"connection_count"` - RxPackets int64 `db:"rx_packets" json:"rx_packets"` - RxBytes int64 `db:"rx_bytes" json:"rx_bytes"` - TxPackets int64 `db:"tx_packets" json:"tx_packets"` - TxBytes int64 `db:"tx_bytes" json:"tx_bytes"` - ConnectionMedianLatencyMS float64 `db:"connection_median_latency_ms" json:"connection_median_latency_ms"` - SessionCountVSCode int64 `db:"session_count_vscode" json:"session_count_vscode"` - SessionCountJetBrains int64 `db:"session_count_jetbrains" json:"session_count_jetbrains"` - SessionCountReconnectingPTY int64 `db:"session_count_reconnecting_pty" json:"session_count_reconnecting_pty"` - SessionCountSSH int64 `db:"session_count_ssh" json:"session_count_ssh"` - Usage bool `db:"usage" json:"usage"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + ConnectionsByProto json.RawMessage `db:"connections_by_proto" json:"connections_by_proto"` + ConnectionCount int64 `db:"connection_count" json:"connection_count"` + RxPackets int64 `db:"rx_packets" json:"rx_packets"` + RxBytes int64 `db:"rx_bytes" json:"rx_bytes"` + TxPackets int64 `db:"tx_packets" json:"tx_packets"` + TxBytes int64 `db:"tx_bytes" json:"tx_bytes"` + ConnectionMedianLatencyMS float64 `db:"connection_median_latency_ms" json:"connection_median_latency_ms"` + Usage bool `db:"usage" json:"usage"` + // Positive session counts keyed by the canonical app name reported by the agent. + SessionCounts json.RawMessage `db:"session_counts" json:"session_counts"` } type WorkspaceAgentVolumeResourceMonitor struct { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 0ebccc223a1..ce74605e121 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -977,7 +977,6 @@ type sqlcQuerier interface { GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]GetWorkspaceAgentScriptsByAgentIDsRow, error) GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error) GetWorkspaceAgentStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsAndLabelsRow, error) - // `minute_buckets` could return 0 rows if there are no usage stats since `created_at`. GetWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsRow, error) GetWorkspaceAgentUsageStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsAndLabelsRow, error) GetWorkspaceAgentsByInstanceID(ctx context.Context, authInstanceID string) ([]WorkspaceAgent, error) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index b6ac7c3c589..cd722cde196 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -58,14 +58,12 @@ func TestGetDeploymentWorkspaceAgentStats(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 1, - SessionCountVSCode: 1, - }) + }, map[string]int64{"vscode": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 2, - SessionCountVSCode: 1, - }) + }, map[string]int64{"vscode": 1}) stats, err := db.GetDeploymentWorkspaceAgentStats(ctx, dbtime.Now().Add(-time.Hour)) require.NoError(t, err) @@ -92,8 +90,7 @@ func TestGetDeploymentWorkspaceAgentStats(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 1, - SessionCountVSCode: 1, - }) + }, map[string]int64{"vscode": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ // Ensure this stat is newer! CreatedAt: insertTime, @@ -101,8 +98,7 @@ func TestGetDeploymentWorkspaceAgentStats(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 2, - SessionCountVSCode: 1, - }) + }, map[string]int64{"vscode": 1}) stats, err := db.GetDeploymentWorkspaceAgentStats(ctx, dbtime.Now().Add(-time.Hour)) require.NoError(t, err) @@ -135,22 +131,17 @@ func TestGetDeploymentWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountSSH: 4, - SessionCountVSCode: 3, - }) + }, map[string]int64{"ssh": 4, "vscode": 3}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime.Add(-time.Minute), - AgentID: agentID, - SessionCountVSCode: 1, - Usage: true, - }) + CreatedAt: insertTime.Add(-time.Minute), + AgentID: agentID, + Usage: true, + }, map[string]int64{"vscode": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime.Add(-time.Minute), - AgentID: agentID, - SessionCountReconnectingPTY: 1, - Usage: true, - }) + CreatedAt: insertTime.Add(-time.Minute), + AgentID: agentID, + Usage: true, + }, map[string]int64{"reconnecting_pty": 1}) // Latest stats dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -159,22 +150,17 @@ func TestGetDeploymentWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 2, - // Should be ignored - SessionCountSSH: 3, - SessionCountVSCode: 1, - }) + }, map[string]int64{"ssh": 3, "vscode": 1}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agentID, - SessionCountVSCode: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agentID, + Usage: true, + }, map[string]int64{"vscode": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agentID, - SessionCountSSH: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agentID, + Usage: true, + }, map[string]int64{"ssh": 1}) stats, err := db.GetDeploymentWorkspaceAgentUsageStats(ctx, dbtime.Now().Add(-time.Hour)) require.NoError(t, err) @@ -189,6 +175,34 @@ func TestGetDeploymentWorkspaceAgentUsageStats(t *testing.T) { require.Equal(t, int64(0), stats.SessionCountJetBrains) }) + t.Run("ExcludesStatsBeforeCutoffInSameMinute", func(t *testing.T) { + t.Parallel() + + db, _ := dbtestutil.NewDB(t) + authz := rbac.NewAuthorizer(prometheus.NewRegistry()) + db = dbauthz.New(db, authz, slogtest.Make(t, &slogtest.Options{}), coderdtest.AccessControlStorePointer()) + ctx := context.Background() + agentID := uuid.New() + minute := dbtime.Now().Add(-2 * time.Minute).Truncate(time.Minute) + cutoff := minute.Add(30 * time.Second) + + dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ + CreatedAt: minute.Add(10 * time.Second), + AgentID: agentID, + Usage: true, + }, map[string]int64{"vscode": 4}) + dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ + CreatedAt: minute.Add(40 * time.Second), + AgentID: agentID, + Usage: true, + }, map[string]int64{"ssh": 1}) + + stats, err := db.GetDeploymentWorkspaceAgentUsageStats(ctx, cutoff) + require.NoError(t, err) + require.Zero(t, stats.SessionCountVSCode) + require.Equal(t, int64(1), stats.SessionCountSSH) + }) + t.Run("NoUsage", func(t *testing.T) { t.Parallel() @@ -206,10 +220,7 @@ func TestGetDeploymentWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 3, RxBytes: 4, ConnectionMedianLatencyMS: 2, - // Should be ignored - SessionCountSSH: 3, - SessionCountVSCode: 1, - }) + }, map[string]int64{"ssh": 3, "vscode": 1}) // Should be ignored. stats, err := db.GetDeploymentWorkspaceAgentUsageStats(ctx, dbtime.Now().Add(-time.Hour)) require.NoError(t, err) @@ -709,6 +720,62 @@ func TestGetProvisionerDaemonsWithStatusByOrganization(t *testing.T) { }) } +func TestGetTemplateInsightsByTemplate(t *testing.T) { + t.Parallel() + + db, _ := dbtestutil.NewDB(t) + ctx := context.Background() + startTime := dbtime.Now().Add(-10 * time.Minute).Truncate(time.Minute) + endTime := startTime.Add(2 * time.Minute) + + insertStat := func(offset time.Duration, templateID, userID, workspaceID uuid.UUID, connections int64, counts map[string]int64) { + dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ + CreatedAt: startTime.Add(offset), + TemplateID: templateID, + UserID: userID, + WorkspaceID: workspaceID, + AgentID: uuid.New(), + ConnectionCount: connections, + }, counts) + } + + templateID := uuid.New() + userID := uuid.New() + workspaceID := uuid.New() + otherWorkspaceID := uuid.New() + + // Activity is deduplicated by user and minute. + insertStat(5*time.Second, templateID, userID, workspaceID, 1, map[string]int64{"ssh": 1, "vscode": 1}) + insertStat(20*time.Second, templateID, userID, workspaceID, 0, map[string]int64{"jetbrains": 1, "vscode": 1}) + insertStat(40*time.Second, templateID, userID, otherWorkspaceID, 0, map[string]int64{"reconnecting_pty": 1, "vscode": 1}) + insertStat(time.Minute+5*time.Second, templateID, userID, otherWorkspaceID, 1, map[string]int64{"ssh": 1, "vscode": 1}) + + // Unknown apps do not contribute activity or active users. + insertStat(10*time.Second, templateID, uuid.New(), uuid.New(), 1, map[string]int64{"unknown": 1}) + + // Unknown activity cannot supply a connection for known activity. + noKnownConnectionTemplateID := uuid.New() + noKnownConnectionUserID := uuid.New() + insertStat(15*time.Second, noKnownConnectionTemplateID, noKnownConnectionUserID, uuid.New(), 0, map[string]int64{"vscode": 1}) + insertStat(30*time.Second, noKnownConnectionTemplateID, noKnownConnectionUserID, uuid.New(), 1, map[string]int64{"unknown": 1}) + + insights, err := db.GetTemplateInsightsByTemplate(ctx, database.GetTemplateInsightsByTemplateParams{ + StartTime: startTime, + EndTime: endTime, + }) + require.NoError(t, err) + require.Equal(t, []database.GetTemplateInsightsByTemplateRow{ + { + TemplateID: templateID, + ActiveUsers: 1, + UsageVscodeSeconds: 120, + UsageJetbrainsSeconds: 60, + UsageReconnectingPtySeconds: 60, + UsageSshSeconds: 120, + }, + }, insights) +} + func TestGetWorkspaceAgentUsageStats(t *testing.T) { t.Parallel() @@ -741,19 +808,15 @@ func TestGetWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountVSCode: 3, - SessionCountSSH: 1, - }) + }, map[string]int64{"vscode": 3, "ssh": 1}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime.Add(-time.Minute), - AgentID: agentID1, - WorkspaceID: workspaceID1, - TemplateID: templateID1, - UserID: userID1, - SessionCountVSCode: 1, - Usage: true, - }) + CreatedAt: insertTime.Add(-time.Minute), + AgentID: agentID1, + WorkspaceID: workspaceID1, + TemplateID: templateID1, + UserID: userID1, + Usage: true, + }, map[string]int64{"vscode": 1}) // Latest workspace 1 stats dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -765,28 +828,23 @@ func TestGetWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 2, RxBytes: 2, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountVSCode: 3, - SessionCountSSH: 4, - }) + }, map[string]int64{"vscode": 3, "ssh": 4}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agentID1, - WorkspaceID: workspaceID1, - TemplateID: templateID1, - UserID: userID1, - SessionCountVSCode: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agentID1, + WorkspaceID: workspaceID1, + TemplateID: templateID1, + UserID: userID1, + Usage: true, + }, map[string]int64{"vscode": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agentID1, - WorkspaceID: workspaceID1, - TemplateID: templateID1, - UserID: userID1, - SessionCountJetBrains: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agentID1, + WorkspaceID: workspaceID1, + TemplateID: templateID1, + UserID: userID1, + Usage: true, + }, map[string]int64{"jetbrains": 1}) // Latest workspace 2 stats dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -808,28 +866,23 @@ func TestGetWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 2, RxBytes: 3, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountVSCode: 3, - SessionCountSSH: 4, - }) + }, map[string]int64{"vscode": 3, "ssh": 4}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agentID2, - WorkspaceID: workspaceID2, - TemplateID: templateID2, - UserID: userID2, - SessionCountSSH: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agentID2, + WorkspaceID: workspaceID2, + TemplateID: templateID2, + UserID: userID2, + Usage: true, + }, map[string]int64{"ssh": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agentID2, - WorkspaceID: workspaceID2, - TemplateID: templateID2, - UserID: userID2, - SessionCountJetBrains: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agentID2, + WorkspaceID: workspaceID2, + TemplateID: templateID2, + UserID: userID2, + Usage: true, + }, map[string]int64{"jetbrains": 1}) reqTime := dbtime.Now().Add(-time.Hour) stats, err := db.GetWorkspaceAgentUsageStats(ctx, reqTime) @@ -872,10 +925,7 @@ func TestGetWorkspaceAgentUsageStats(t *testing.T) { TxBytes: 3, RxBytes: 4, ConnectionMedianLatencyMS: 2, - // Should be ignored - SessionCountSSH: 3, - SessionCountVSCode: 1, - }) + }, map[string]int64{"ssh": 3, "vscode": 1}) // Should be ignored. stats, err := db.GetWorkspaceAgentUsageStats(ctx, dbtime.Now().Add(-time.Hour)) require.NoError(t, err) @@ -1039,19 +1089,15 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { TxBytes: 1, RxBytes: 1, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountVSCode: 3, - SessionCountSSH: 1, - }) + }, map[string]int64{"vscode": 3, "ssh": 1}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime.Add(-time.Minute), - AgentID: agent1.ID, - WorkspaceID: workspace1.ID, - TemplateID: template1.ID, - UserID: user1.ID, - SessionCountVSCode: 1, - Usage: true, - }) + CreatedAt: insertTime.Add(-time.Minute), + AgentID: agent1.ID, + WorkspaceID: workspace1.ID, + TemplateID: template1.ID, + UserID: user1.ID, + Usage: true, + }, map[string]int64{"vscode": 1}) // Latest workspace 1 stats dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -1063,28 +1109,23 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { TxBytes: 2, RxBytes: 2, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountVSCode: 4, - SessionCountSSH: 3, - }) + }, map[string]int64{"vscode": 4, "ssh": 3}) // Should be ignored. dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agent1.ID, - WorkspaceID: workspace1.ID, - TemplateID: template1.ID, - UserID: user1.ID, - SessionCountJetBrains: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agent1.ID, + WorkspaceID: workspace1.ID, + TemplateID: template1.ID, + UserID: user1.ID, + Usage: true, + }, map[string]int64{"jetbrains": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agent1.ID, - WorkspaceID: workspace1.ID, - TemplateID: template1.ID, - UserID: user1.ID, - SessionCountReconnectingPTY: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agent1.ID, + WorkspaceID: workspace1.ID, + TemplateID: template1.ID, + UserID: user1.ID, + Usage: true, + }, map[string]int64{"reconnecting_pty": 1}) // Latest workspace 2 stats dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ @@ -1098,23 +1139,21 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { ConnectionMedianLatencyMS: 1, }) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agent2.ID, - WorkspaceID: workspace2.ID, - TemplateID: template2.ID, - UserID: user2.ID, - SessionCountVSCode: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agent2.ID, + WorkspaceID: workspace2.ID, + TemplateID: template2.ID, + UserID: user2.ID, + Usage: true, + }, map[string]int64{"vscode": 1}) dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ - CreatedAt: insertTime, - AgentID: agent2.ID, - WorkspaceID: workspace2.ID, - TemplateID: template2.ID, - UserID: user2.ID, - SessionCountSSH: 1, - Usage: true, - }) + CreatedAt: insertTime, + AgentID: agent2.ID, + WorkspaceID: workspace2.ID, + TemplateID: template2.ID, + UserID: user2.ID, + Usage: true, + }, map[string]int64{"ssh": 1}) stats, err := db.GetWorkspaceAgentUsageStatsAndLabels(ctx, insertTime.Add(-time.Hour)) require.NoError(t, err) @@ -1180,10 +1219,7 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { RxBytes: 4, TxBytes: 5, ConnectionMedianLatencyMS: 1, - // Should be ignored - SessionCountVSCode: 3, - SessionCountSSH: 1, - }) + }, map[string]int64{"vscode": 3, "ssh": 1}) // Should be ignored. stats, err := db.GetWorkspaceAgentUsageStatsAndLabels(ctx, insertTime.Add(-time.Hour)) require.NoError(t, err) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 9434475b872..562f046beda 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -15961,39 +15961,41 @@ func (q *sqlQuerier) GetTemplateInsightsByInterval(ctx context.Context, arg GetT const getTemplateInsightsByTemplate = `-- name: GetTemplateInsightsByTemplate :many WITH - -- This CTE is used to truncate agent usage into minute buckets, then - -- flatten the users agent usage within the template so that usage in - -- multiple workspaces under one template is only counted once for - -- every minute (per user). + -- Deduplicate activity by template, user, and minute. + minute_activity AS ( + SELECT + template_id, + user_id, + date_trunc('minute', created_at) AS minute, + BOOL_OR(session_counts ? 'ssh') AS ssh, + BOOL_OR(session_counts ? 'reconnecting_pty') AS reconnecting_pty, + BOOL_OR(session_counts ? 'vscode') AS vscode, + BOOL_OR(session_counts ? 'jetbrains') AS jetbrains, + BOOL_OR(connection_count > 0) AS has_connection + FROM + workspace_agent_stats + WHERE + created_at >= $1::timestamptz + AND created_at < $2::timestamptz + AND session_counts ?| ARRAY['ssh', 'reconnecting_pty', 'vscode', 'jetbrains'] + GROUP BY + template_id, user_id, minute + ), insights AS ( SELECT template_id, user_id, - COUNT(DISTINCT CASE WHEN session_count_ssh > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, - -- TODO(mafredri): Enable when we have the column. - -- COUNT(DISTINCT CASE WHEN session_count_sftp > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS sftp_mins, - COUNT(DISTINCT CASE WHEN session_count_reconnecting_pty > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, - COUNT(DISTINCT CASE WHEN session_count_vscode > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, - COUNT(DISTINCT CASE WHEN session_count_jetbrains > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, + COUNT(*) FILTER (WHERE ssh) AS ssh_mins, + COUNT(*) FILTER (WHERE reconnecting_pty) AS reconnecting_pty_mins, + COUNT(*) FILTER (WHERE vscode) AS vscode_mins, + COUNT(*) FILTER (WHERE jetbrains) AS jetbrains_mins, -- NOTE(mafredri): The agent stats are currently very unreliable, and -- sometimes the connections are missing, even during active sessions. -- Since we can't fully rely on this, we check for "any connection -- within this bucket". A better solution here would be preferable. - MAX(connection_count) > 0 AS has_connection + BOOL_OR(has_connection) AS has_connection FROM - workspace_agent_stats - WHERE - created_at >= $1::timestamptz - AND created_at < $2::timestamptz - -- Inclusion criteria to filter out empty results. - AND ( - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - ) + minute_activity GROUP BY template_id, user_id ) @@ -16603,27 +16605,11 @@ WITH template_id, user_id, -- Store each unique minute bucket for later merge between datasets. - array_agg( - DISTINCT CASE - WHEN - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - THEN - date_trunc('minute', created_at) - ELSE - NULL - END - ) AS minute_buckets, - COUNT(DISTINCT CASE WHEN session_count_ssh > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, - -- TODO(mafredri): Enable when we have the column. - -- COUNT(DISTINCT CASE WHEN session_count_sftp > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS sftp_mins, - COUNT(DISTINCT CASE WHEN session_count_reconnecting_pty > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, - COUNT(DISTINCT CASE WHEN session_count_vscode > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, - COUNT(DISTINCT CASE WHEN session_count_jetbrains > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, + array_agg(DISTINCT date_trunc('minute', created_at)) AS minute_buckets, + COUNT(DISTINCT CASE WHEN session_counts ? 'ssh' THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, + COUNT(DISTINCT CASE WHEN session_counts ? 'reconnecting_pty' THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, + COUNT(DISTINCT CASE WHEN session_counts ? 'vscode' THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, + COUNT(DISTINCT CASE WHEN session_counts ? 'jetbrains' THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, -- NOTE(mafredri): The agent stats are currently very unreliable, and -- sometimes the connections are missing, even during active sessions. -- Since we can't fully rely on this, we check for "any connection @@ -16636,15 +16622,7 @@ WITH -- AND created_at < @end_time::timestamptz created_at >= (SELECT t FROM latest_start) AND created_at < NOW() - -- Inclusion criteria to filter out empty results. - AND ( - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - ) + AND session_counts ?| ARRAY['ssh', 'reconnecting_pty', 'vscode', 'jetbrains'] GROUP BY time_bucket, template_id, user_id ), @@ -34767,30 +34745,27 @@ func (q *sqlQuerier) DeleteOldWorkspaceAgentStats(ctx context.Context) error { const getDeploymentWorkspaceAgentStats = `-- name: GetDeploymentWorkspaceAgentStats :one WITH stats AS ( - SELECT - agent_id, - created_at, - rx_bytes, - tx_bytes, - connection_median_latency_ms, - session_count_vscode, - session_count_ssh, - session_count_jetbrains, - session_count_reconnecting_pty, - ROW_NUMBER() OVER (PARTITION BY agent_id ORDER BY created_at DESC) AS rn - FROM workspace_agent_stats - WHERE created_at > $1 + SELECT + agent_id, + created_at, + rx_bytes, + tx_bytes, + connection_median_latency_ms, + session_counts, + ROW_NUMBER() OVER (PARTITION BY agent_id ORDER BY created_at DESC) AS rn + FROM workspace_agent_stats + WHERE created_at > $1 ) SELECT - coalesce(SUM(rx_bytes), 0)::bigint AS workspace_rx_bytes, - coalesce(SUM(tx_bytes), 0)::bigint AS workspace_tx_bytes, - -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_50, - coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_95, - coalesce(SUM(session_count_vscode) FILTER (WHERE rn = 1), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh) FILTER (WHERE rn = 1), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains) FILTER (WHERE rn = 1), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty) FILTER (WHERE rn = 1), 0)::bigint AS session_count_reconnecting_pty + coalesce(SUM(rx_bytes), 0)::bigint AS workspace_rx_bytes, + coalesce(SUM(tx_bytes), 0)::bigint AS workspace_tx_bytes, + -- Positive latency values exclude legacy agents that do not report latency. + coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_50, + coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_95, + coalesce(SUM((session_counts ->> 'vscode')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_vscode, + coalesce(SUM((session_counts ->> 'ssh')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_ssh, + coalesce(SUM((session_counts ->> 'jetbrains')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((session_counts ->> 'reconnecting_pty')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_reconnecting_pty FROM stats ` @@ -34832,46 +34807,36 @@ WITH agent_stats AS ( -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 ), -minute_buckets AS ( +latest_minutes AS ( SELECT agent_id, - date_trunc('minute', created_at) AS minute_bucket, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty + MAX(date_trunc('minute', created_at)) AS minute_bucket FROM workspace_agent_stats WHERE created_at >= $1 - AND created_at < date_trunc('minute', now()) -- Exclude current partial minute - AND usage = true + -- Exclude the current partial minute. + AND created_at < date_trunc('minute', now()) + AND usage GROUP BY - agent_id, - minute_bucket -), -latest_buckets AS ( - SELECT DISTINCT ON (agent_id) - agent_id, - minute_bucket, - session_count_vscode, - session_count_jetbrains, - session_count_reconnecting_pty, - session_count_ssh - FROM - minute_buckets - ORDER BY - agent_id, - minute_bucket DESC + agent_id ), latest_agent_stats AS ( - SELECT - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty - FROM - latest_buckets + SELECT + coalesce(SUM((stats.session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((stats.session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((stats.session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((stats.session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty + FROM + latest_minutes + JOIN + workspace_agent_stats AS stats + ON + stats.agent_id = latest_minutes.agent_id + AND stats.created_at >= $1 + AND stats.created_at >= latest_minutes.minute_bucket + AND stats.created_at < latest_minutes.minute_bucket + '1 minute'::interval + AND stats.usage ) SELECT workspace_rx_bytes, workspace_tx_bytes, workspace_connection_latency_50, workspace_connection_latency_95, session_count_vscode, session_count_ssh, session_count_jetbrains, session_count_reconnecting_pty FROM agent_stats, latest_agent_stats ` @@ -34922,14 +34887,16 @@ WITH agent_stats AS ( ), latest_agent_stats AS ( SELECT a.agent_id, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty + coalesce(SUM((a.session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((a.session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((a.session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((a.session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty FROM ( - SELECT id, created_at, user_id, agent_id, workspace_id, template_id, connections_by_proto, connection_count, rx_packets, rx_bytes, tx_packets, tx_bytes, connection_median_latency_ms, session_count_vscode, session_count_jetbrains, session_count_reconnecting_pty, session_count_ssh, usage, ROW_NUMBER() OVER(PARTITION BY agent_id ORDER BY created_at DESC) AS rn + SELECT id, created_at, user_id, agent_id, workspace_id, template_id, connections_by_proto, connection_count, rx_packets, rx_bytes, tx_packets, tx_bytes, connection_median_latency_ms, usage, session_counts, ROW_NUMBER() OVER(PARTITION BY agent_id ORDER BY created_at DESC) AS rn FROM workspace_agent_stats WHERE created_at > $1 - ) AS a WHERE a.rn = 1 GROUP BY a.user_id, a.agent_id, a.workspace_id, a.template_id + ) AS a + WHERE a.rn = 1 + GROUP BY a.user_id, a.agent_id, a.workspace_id, a.template_id ) SELECT user_id, agent_stats.agent_id, workspace_id, template_id, aggregated_from, workspace_rx_bytes, workspace_tx_bytes, workspace_connection_latency_50, workspace_connection_latency_95, latest_agent_stats.agent_id, session_count_vscode, session_count_ssh, session_count_jetbrains, session_count_reconnecting_pty FROM agent_stats JOIN latest_agent_stats ON agent_stats.agent_id = latest_agent_stats.agent_id ` @@ -35003,14 +34970,14 @@ WITH agent_stats AS ( ), latest_agent_stats AS ( SELECT a.agent_id, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty, - coalesce(SUM(connection_count), 0)::bigint AS connection_count, - coalesce(MAX(connection_median_latency_ms), 0)::float AS connection_median_latency_ms + coalesce(SUM((a.session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((a.session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((a.session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((a.session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty, + coalesce(SUM(a.connection_count), 0)::bigint AS connection_count, + coalesce(MAX(a.connection_median_latency_ms), 0)::float AS connection_median_latency_ms FROM ( - SELECT id, created_at, user_id, agent_id, workspace_id, template_id, connections_by_proto, connection_count, rx_packets, rx_bytes, tx_packets, tx_bytes, connection_median_latency_ms, session_count_vscode, session_count_jetbrains, session_count_reconnecting_pty, session_count_ssh, usage, ROW_NUMBER() OVER(PARTITION BY agent_id ORDER BY created_at DESC) AS rn + SELECT id, created_at, user_id, agent_id, workspace_id, template_id, connections_by_proto, connection_count, rx_packets, rx_bytes, tx_packets, tx_bytes, connection_median_latency_ms, usage, session_counts, ROW_NUMBER() OVER(PARTITION BY agent_id ORDER BY created_at DESC) AS rn FROM workspace_agent_stats -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. WHERE created_at > $1 AND connection_median_latency_ms > 0 @@ -35092,72 +35059,60 @@ func (q *sqlQuerier) GetWorkspaceAgentStatsAndLabels(ctx context.Context, create } const getWorkspaceAgentUsageStats = `-- name: GetWorkspaceAgentUsageStats :many -WITH agent_stats AS ( +WITH stats AS ( SELECT user_id, agent_id, workspace_id, template_id, - MIN(created_at)::timestamptz AS aggregated_from, - coalesce(SUM(rx_bytes), 0)::bigint AS workspace_rx_bytes, - coalesce(SUM(tx_bytes), 0)::bigint AS workspace_tx_bytes, - coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_50, - coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_95 + created_at, + rx_bytes, + tx_bytes, + connection_median_latency_ms, + usage, + session_counts, + MAX(date_trunc('minute', created_at)) FILTER ( + WHERE usage AND created_at < date_trunc('minute', now()) + ) OVER (PARTITION BY agent_id) AS latest_usage_minute FROM workspace_agent_stats - -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 - GROUP BY user_id, agent_id, workspace_id, template_id -), -minute_buckets AS ( - SELECT - agent_id, - date_trunc('minute', created_at) AS minute_bucket, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty - FROM - workspace_agent_stats - WHERE - created_at >= $1 - AND created_at < date_trunc('minute', now()) -- Exclude current partial minute - AND usage = true - GROUP BY - agent_id, - minute_bucket, - user_id, - agent_id, - workspace_id, - template_id -), -latest_buckets AS ( - SELECT DISTINCT ON (agent_id) - agent_id, - session_count_vscode, - session_count_ssh, - session_count_jetbrains, - session_count_reconnecting_pty - FROM - minute_buckets - ORDER BY - agent_id, - minute_bucket DESC + WHERE created_at >= $1 ) -SELECT user_id, -agent_stats.agent_id, -workspace_id, -template_id, -aggregated_from, -workspace_rx_bytes, -workspace_tx_bytes, -workspace_connection_latency_50, -workspace_connection_latency_95, -coalesce(latest_buckets.agent_id,agent_stats.agent_id) AS agent_id, -coalesce(session_count_vscode, 0)::bigint AS session_count_vscode, -coalesce(session_count_ssh, 0)::bigint AS session_count_ssh, -coalesce(session_count_jetbrains, 0)::bigint AS session_count_jetbrains, -coalesce(session_count_reconnecting_pty, 0)::bigint AS session_count_reconnecting_pty -FROM agent_stats LEFT JOIN latest_buckets ON agent_stats.agent_id = latest_buckets.agent_id +SELECT + user_id, + agent_id, + workspace_id, + template_id, + MIN(created_at) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + )::timestamptz AS aggregated_from, + coalesce(SUM(rx_bytes) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + ), 0)::bigint AS workspace_rx_bytes, + coalesce(SUM(tx_bytes) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + ), 0)::bigint AS workspace_tx_bytes, + coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + )), -1)::FLOAT AS workspace_connection_latency_50, + coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + )), -1)::FLOAT AS workspace_connection_latency_95, + agent_id, + coalesce(SUM((session_counts ->> 'vscode')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_vscode, + coalesce(SUM((session_counts ->> 'ssh')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_ssh, + coalesce(SUM((session_counts ->> 'jetbrains')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((session_counts ->> 'reconnecting_pty')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_reconnecting_pty +FROM stats +GROUP BY user_id, agent_id, workspace_id, template_id +HAVING BOOL_OR(created_at > $1 AND connection_median_latency_ms > 0) ` type GetWorkspaceAgentUsageStatsRow struct { @@ -35177,7 +35132,6 @@ type GetWorkspaceAgentUsageStatsRow struct { SessionCountReconnectingPTY int64 `db:"session_count_reconnecting_pty" json:"session_count_reconnecting_pty"` } -// `minute_buckets` could return 0 rows if there are no usage stats since `created_at`. func (q *sqlQuerier) GetWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsRow, error) { rows, err := q.db.QueryContext(ctx, getWorkspaceAgentUsageStats, createdAt) if err != nil { @@ -35232,15 +35186,15 @@ WITH agent_stats AS ( ), latest_agent_stats AS ( SELECT agent_id, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty, + coalesce(SUM((session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty, coalesce(SUM(connection_count), 0)::bigint AS connection_count FROM workspace_agent_stats -- We only want the latest stats, but those stats might be -- spread across multiple rows. - WHERE usage = true AND created_at > now() - '1 minute'::interval + WHERE usage AND created_at > now() - '1 minute'::interval GROUP BY user_id, agent_id, workspace_id ) SELECT @@ -35335,10 +35289,7 @@ INSERT INTO rx_bytes, tx_packets, tx_bytes, - session_count_vscode, - session_count_jetbrains, - session_count_reconnecting_pty, - session_count_ssh, + session_counts, connection_median_latency_ms, usage ) @@ -35355,33 +35306,27 @@ SELECT unnest($10 :: bigint[]) AS rx_bytes, unnest($11 :: bigint[]) AS tx_packets, unnest($12 :: bigint[]) AS tx_bytes, - unnest($13 :: bigint[]) AS session_count_vscode, - unnest($14 :: bigint[]) AS session_count_jetbrains, - unnest($15 :: bigint[]) AS session_count_reconnecting_pty, - unnest($16 :: bigint[]) AS session_count_ssh, - unnest($17 :: double precision[]) AS connection_median_latency_ms, - unnest($18 :: boolean[]) AS usage + jsonb_array_elements($13 :: jsonb) AS session_counts, + unnest($14 :: double precision[]) AS connection_median_latency_ms, + unnest($15 :: boolean[]) AS usage ` type InsertWorkspaceAgentStatsParams struct { - ID []uuid.UUID `db:"id" json:"id"` - CreatedAt []time.Time `db:"created_at" json:"created_at"` - UserID []uuid.UUID `db:"user_id" json:"user_id"` - WorkspaceID []uuid.UUID `db:"workspace_id" json:"workspace_id"` - TemplateID []uuid.UUID `db:"template_id" json:"template_id"` - AgentID []uuid.UUID `db:"agent_id" json:"agent_id"` - ConnectionsByProto json.RawMessage `db:"connections_by_proto" json:"connections_by_proto"` - ConnectionCount []int64 `db:"connection_count" json:"connection_count"` - RxPackets []int64 `db:"rx_packets" json:"rx_packets"` - RxBytes []int64 `db:"rx_bytes" json:"rx_bytes"` - TxPackets []int64 `db:"tx_packets" json:"tx_packets"` - TxBytes []int64 `db:"tx_bytes" json:"tx_bytes"` - SessionCountVSCode []int64 `db:"session_count_vscode" json:"session_count_vscode"` - SessionCountJetBrains []int64 `db:"session_count_jetbrains" json:"session_count_jetbrains"` - SessionCountReconnectingPTY []int64 `db:"session_count_reconnecting_pty" json:"session_count_reconnecting_pty"` - SessionCountSSH []int64 `db:"session_count_ssh" json:"session_count_ssh"` - ConnectionMedianLatencyMS []float64 `db:"connection_median_latency_ms" json:"connection_median_latency_ms"` - Usage []bool `db:"usage" json:"usage"` + ID []uuid.UUID `db:"id" json:"id"` + CreatedAt []time.Time `db:"created_at" json:"created_at"` + UserID []uuid.UUID `db:"user_id" json:"user_id"` + WorkspaceID []uuid.UUID `db:"workspace_id" json:"workspace_id"` + TemplateID []uuid.UUID `db:"template_id" json:"template_id"` + AgentID []uuid.UUID `db:"agent_id" json:"agent_id"` + ConnectionsByProto json.RawMessage `db:"connections_by_proto" json:"connections_by_proto"` + ConnectionCount []int64 `db:"connection_count" json:"connection_count"` + RxPackets []int64 `db:"rx_packets" json:"rx_packets"` + RxBytes []int64 `db:"rx_bytes" json:"rx_bytes"` + TxPackets []int64 `db:"tx_packets" json:"tx_packets"` + TxBytes []int64 `db:"tx_bytes" json:"tx_bytes"` + SessionCounts json.RawMessage `db:"session_counts" json:"session_counts"` + ConnectionMedianLatencyMS []float64 `db:"connection_median_latency_ms" json:"connection_median_latency_ms"` + Usage []bool `db:"usage" json:"usage"` } func (q *sqlQuerier) InsertWorkspaceAgentStats(ctx context.Context, arg InsertWorkspaceAgentStatsParams) error { @@ -35398,10 +35343,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentStats(ctx context.Context, arg InsertWo pq.Array(arg.RxBytes), pq.Array(arg.TxPackets), pq.Array(arg.TxBytes), - pq.Array(arg.SessionCountVSCode), - pq.Array(arg.SessionCountJetBrains), - pq.Array(arg.SessionCountReconnectingPTY), - pq.Array(arg.SessionCountSSH), + arg.SessionCounts, pq.Array(arg.ConnectionMedianLatencyMS), pq.Array(arg.Usage), ) diff --git a/coderd/database/queries/insights.sql b/coderd/database/queries/insights.sql index b589ce4e9a6..6e61b95bf08 100644 --- a/coderd/database/queries/insights.sql +++ b/coderd/database/queries/insights.sql @@ -148,39 +148,41 @@ FROM -- GetTemplateInsightsByTemplate is used for Prometheus metrics. Keep -- in sync with GetTemplateInsights and UpsertTemplateUsageStats. WITH - -- This CTE is used to truncate agent usage into minute buckets, then - -- flatten the users agent usage within the template so that usage in - -- multiple workspaces under one template is only counted once for - -- every minute (per user). + -- Deduplicate activity by template, user, and minute. + minute_activity AS ( + SELECT + template_id, + user_id, + date_trunc('minute', created_at) AS minute, + BOOL_OR(session_counts ? 'ssh') AS ssh, + BOOL_OR(session_counts ? 'reconnecting_pty') AS reconnecting_pty, + BOOL_OR(session_counts ? 'vscode') AS vscode, + BOOL_OR(session_counts ? 'jetbrains') AS jetbrains, + BOOL_OR(connection_count > 0) AS has_connection + FROM + workspace_agent_stats + WHERE + created_at >= @start_time::timestamptz + AND created_at < @end_time::timestamptz + AND session_counts ?| ARRAY['ssh', 'reconnecting_pty', 'vscode', 'jetbrains'] + GROUP BY + template_id, user_id, minute + ), insights AS ( SELECT template_id, user_id, - COUNT(DISTINCT CASE WHEN session_count_ssh > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, - -- TODO(mafredri): Enable when we have the column. - -- COUNT(DISTINCT CASE WHEN session_count_sftp > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS sftp_mins, - COUNT(DISTINCT CASE WHEN session_count_reconnecting_pty > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, - COUNT(DISTINCT CASE WHEN session_count_vscode > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, - COUNT(DISTINCT CASE WHEN session_count_jetbrains > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, + COUNT(*) FILTER (WHERE ssh) AS ssh_mins, + COUNT(*) FILTER (WHERE reconnecting_pty) AS reconnecting_pty_mins, + COUNT(*) FILTER (WHERE vscode) AS vscode_mins, + COUNT(*) FILTER (WHERE jetbrains) AS jetbrains_mins, -- NOTE(mafredri): The agent stats are currently very unreliable, and -- sometimes the connections are missing, even during active sessions. -- Since we can't fully rely on this, we check for "any connection -- within this bucket". A better solution here would be preferable. - MAX(connection_count) > 0 AS has_connection + BOOL_OR(has_connection) AS has_connection FROM - workspace_agent_stats - WHERE - created_at >= @start_time::timestamptz - AND created_at < @end_time::timestamptz - -- Inclusion criteria to filter out empty results. - AND ( - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - ) + minute_activity GROUP BY template_id, user_id ) @@ -559,27 +561,11 @@ WITH template_id, user_id, -- Store each unique minute bucket for later merge between datasets. - array_agg( - DISTINCT CASE - WHEN - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - THEN - date_trunc('minute', created_at) - ELSE - NULL - END - ) AS minute_buckets, - COUNT(DISTINCT CASE WHEN session_count_ssh > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, - -- TODO(mafredri): Enable when we have the column. - -- COUNT(DISTINCT CASE WHEN session_count_sftp > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS sftp_mins, - COUNT(DISTINCT CASE WHEN session_count_reconnecting_pty > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, - COUNT(DISTINCT CASE WHEN session_count_vscode > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, - COUNT(DISTINCT CASE WHEN session_count_jetbrains > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, + array_agg(DISTINCT date_trunc('minute', created_at)) AS minute_buckets, + COUNT(DISTINCT CASE WHEN session_counts ? 'ssh' THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, + COUNT(DISTINCT CASE WHEN session_counts ? 'reconnecting_pty' THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, + COUNT(DISTINCT CASE WHEN session_counts ? 'vscode' THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, + COUNT(DISTINCT CASE WHEN session_counts ? 'jetbrains' THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, -- NOTE(mafredri): The agent stats are currently very unreliable, and -- sometimes the connections are missing, even during active sessions. -- Since we can't fully rely on this, we check for "any connection @@ -592,15 +578,7 @@ WITH -- AND created_at < @end_time::timestamptz created_at >= (SELECT t FROM latest_start) AND created_at < NOW() - -- Inclusion criteria to filter out empty results. - AND ( - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - ) + AND session_counts ?| ARRAY['ssh', 'reconnecting_pty', 'vscode', 'jetbrains'] GROUP BY time_bucket, template_id, user_id ), diff --git a/coderd/database/queries/workspaceagentstats.sql b/coderd/database/queries/workspaceagentstats.sql index 28c17d8271e..a0c6298f5cb 100644 --- a/coderd/database/queries/workspaceagentstats.sql +++ b/coderd/database/queries/workspaceagentstats.sql @@ -13,10 +13,7 @@ INSERT INTO rx_bytes, tx_packets, tx_bytes, - session_count_vscode, - session_count_jetbrains, - session_count_reconnecting_pty, - session_count_ssh, + session_counts, connection_median_latency_ms, usage ) @@ -33,10 +30,7 @@ SELECT unnest(@rx_bytes :: bigint[]) AS rx_bytes, unnest(@tx_packets :: bigint[]) AS tx_packets, unnest(@tx_bytes :: bigint[]) AS tx_bytes, - unnest(@session_count_vscode :: bigint[]) AS session_count_vscode, - unnest(@session_count_jetbrains :: bigint[]) AS session_count_jetbrains, - unnest(@session_count_reconnecting_pty :: bigint[]) AS session_count_reconnecting_pty, - unnest(@session_count_ssh :: bigint[]) AS session_count_ssh, + jsonb_array_elements(@session_counts :: jsonb) AS session_counts, unnest(@connection_median_latency_ms :: double precision[]) AS connection_median_latency_ms, unnest(@usage :: boolean[]) AS usage; @@ -73,30 +67,27 @@ WHERE -- name: GetDeploymentWorkspaceAgentStats :one WITH stats AS ( - SELECT - agent_id, - created_at, - rx_bytes, - tx_bytes, - connection_median_latency_ms, - session_count_vscode, - session_count_ssh, - session_count_jetbrains, - session_count_reconnecting_pty, - ROW_NUMBER() OVER (PARTITION BY agent_id ORDER BY created_at DESC) AS rn - FROM workspace_agent_stats - WHERE created_at > $1 + SELECT + agent_id, + created_at, + rx_bytes, + tx_bytes, + connection_median_latency_ms, + session_counts, + ROW_NUMBER() OVER (PARTITION BY agent_id ORDER BY created_at DESC) AS rn + FROM workspace_agent_stats + WHERE created_at > $1 ) SELECT - coalesce(SUM(rx_bytes), 0)::bigint AS workspace_rx_bytes, - coalesce(SUM(tx_bytes), 0)::bigint AS workspace_tx_bytes, - -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_50, - coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_95, - coalesce(SUM(session_count_vscode) FILTER (WHERE rn = 1), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh) FILTER (WHERE rn = 1), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains) FILTER (WHERE rn = 1), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty) FILTER (WHERE rn = 1), 0)::bigint AS session_count_reconnecting_pty + coalesce(SUM(rx_bytes), 0)::bigint AS workspace_rx_bytes, + coalesce(SUM(tx_bytes), 0)::bigint AS workspace_tx_bytes, + -- Positive latency values exclude legacy agents that do not report latency. + coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_50, + coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER (WHERE connection_median_latency_ms > 0)), -1)::FLOAT AS workspace_connection_latency_95, + coalesce(SUM((session_counts ->> 'vscode')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_vscode, + coalesce(SUM((session_counts ->> 'ssh')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_ssh, + coalesce(SUM((session_counts ->> 'jetbrains')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((session_counts ->> 'reconnecting_pty')::bigint) FILTER (WHERE rn = 1), 0)::bigint AS session_count_reconnecting_pty FROM stats; -- name: GetDeploymentWorkspaceAgentUsageStats :one @@ -110,46 +101,36 @@ WITH agent_stats AS ( -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 ), -minute_buckets AS ( +latest_minutes AS ( SELECT agent_id, - date_trunc('minute', created_at) AS minute_bucket, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty + MAX(date_trunc('minute', created_at)) AS minute_bucket FROM workspace_agent_stats WHERE created_at >= $1 - AND created_at < date_trunc('minute', now()) -- Exclude current partial minute - AND usage = true + -- Exclude the current partial minute. + AND created_at < date_trunc('minute', now()) + AND usage GROUP BY - agent_id, - minute_bucket -), -latest_buckets AS ( - SELECT DISTINCT ON (agent_id) - agent_id, - minute_bucket, - session_count_vscode, - session_count_jetbrains, - session_count_reconnecting_pty, - session_count_ssh - FROM - minute_buckets - ORDER BY - agent_id, - minute_bucket DESC + agent_id ), latest_agent_stats AS ( - SELECT - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty - FROM - latest_buckets + SELECT + coalesce(SUM((stats.session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((stats.session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((stats.session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((stats.session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty + FROM + latest_minutes + JOIN + workspace_agent_stats AS stats + ON + stats.agent_id = latest_minutes.agent_id + AND stats.created_at >= $1 + AND stats.created_at >= latest_minutes.minute_bucket + AND stats.created_at < latest_minutes.minute_bucket + '1 minute'::interval + AND stats.usage ) SELECT * FROM agent_stats, latest_agent_stats; @@ -172,85 +153,74 @@ WITH agent_stats AS ( ), latest_agent_stats AS ( SELECT a.agent_id, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty + coalesce(SUM((a.session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((a.session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((a.session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((a.session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY agent_id ORDER BY created_at DESC) AS rn FROM workspace_agent_stats WHERE created_at > $1 - ) AS a WHERE a.rn = 1 GROUP BY a.user_id, a.agent_id, a.workspace_id, a.template_id + ) AS a + WHERE a.rn = 1 + GROUP BY a.user_id, a.agent_id, a.workspace_id, a.template_id ) SELECT * FROM agent_stats JOIN latest_agent_stats ON agent_stats.agent_id = latest_agent_stats.agent_id; -- name: GetWorkspaceAgentUsageStats :many -WITH agent_stats AS ( +WITH stats AS ( SELECT user_id, agent_id, workspace_id, template_id, - MIN(created_at)::timestamptz AS aggregated_from, - coalesce(SUM(rx_bytes), 0)::bigint AS workspace_rx_bytes, - coalesce(SUM(tx_bytes), 0)::bigint AS workspace_tx_bytes, - coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_50, - coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_95 + created_at, + rx_bytes, + tx_bytes, + connection_median_latency_ms, + usage, + session_counts, + MAX(date_trunc('minute', created_at)) FILTER ( + WHERE usage AND created_at < date_trunc('minute', now()) + ) OVER (PARTITION BY agent_id) AS latest_usage_minute FROM workspace_agent_stats - -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 - GROUP BY user_id, agent_id, workspace_id, template_id -), -minute_buckets AS ( - SELECT - agent_id, - date_trunc('minute', created_at) AS minute_bucket, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty - FROM - workspace_agent_stats - WHERE - created_at >= $1 - AND created_at < date_trunc('minute', now()) -- Exclude current partial minute - AND usage = true - GROUP BY - agent_id, - minute_bucket, - user_id, - agent_id, - workspace_id, - template_id -), -latest_buckets AS ( - SELECT DISTINCT ON (agent_id) - agent_id, - session_count_vscode, - session_count_ssh, - session_count_jetbrains, - session_count_reconnecting_pty - FROM - minute_buckets - ORDER BY - agent_id, - minute_bucket DESC + WHERE created_at >= $1 ) -SELECT user_id, -agent_stats.agent_id, -workspace_id, -template_id, -aggregated_from, -workspace_rx_bytes, -workspace_tx_bytes, -workspace_connection_latency_50, -workspace_connection_latency_95, --- `minute_buckets` could return 0 rows if there are no usage stats since `created_at`. -coalesce(latest_buckets.agent_id,agent_stats.agent_id) AS agent_id, -coalesce(session_count_vscode, 0)::bigint AS session_count_vscode, -coalesce(session_count_ssh, 0)::bigint AS session_count_ssh, -coalesce(session_count_jetbrains, 0)::bigint AS session_count_jetbrains, -coalesce(session_count_reconnecting_pty, 0)::bigint AS session_count_reconnecting_pty -FROM agent_stats LEFT JOIN latest_buckets ON agent_stats.agent_id = latest_buckets.agent_id; +SELECT + user_id, + agent_id, + workspace_id, + template_id, + MIN(created_at) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + )::timestamptz AS aggregated_from, + coalesce(SUM(rx_bytes) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + ), 0)::bigint AS workspace_rx_bytes, + coalesce(SUM(tx_bytes) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + ), 0)::bigint AS workspace_tx_bytes, + coalesce((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + )), -1)::FLOAT AS workspace_connection_latency_50, + coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms) FILTER ( + WHERE created_at > $1 AND connection_median_latency_ms > 0 + )), -1)::FLOAT AS workspace_connection_latency_95, + agent_id, + coalesce(SUM((session_counts ->> 'vscode')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_vscode, + coalesce(SUM((session_counts ->> 'ssh')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_ssh, + coalesce(SUM((session_counts ->> 'jetbrains')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((session_counts ->> 'reconnecting_pty')::bigint) FILTER ( + WHERE usage AND date_trunc('minute', created_at) = latest_usage_minute + ), 0)::bigint AS session_count_reconnecting_pty +FROM stats +GROUP BY user_id, agent_id, workspace_id, template_id +HAVING BOOL_OR(created_at > $1 AND connection_median_latency_ms > 0); -- name: GetWorkspaceAgentStatsAndLabels :many WITH agent_stats AS ( @@ -266,12 +236,12 @@ WITH agent_stats AS ( ), latest_agent_stats AS ( SELECT a.agent_id, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty, - coalesce(SUM(connection_count), 0)::bigint AS connection_count, - coalesce(MAX(connection_median_latency_ms), 0)::float AS connection_median_latency_ms + coalesce(SUM((a.session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((a.session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((a.session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((a.session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty, + coalesce(SUM(a.connection_count), 0)::bigint AS connection_count, + coalesce(MAX(a.connection_median_latency_ms), 0)::float AS connection_median_latency_ms FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY agent_id ORDER BY created_at DESC) AS rn FROM workspace_agent_stats @@ -320,15 +290,15 @@ WITH agent_stats AS ( ), latest_agent_stats AS ( SELECT agent_id, - coalesce(SUM(session_count_vscode), 0)::bigint AS session_count_vscode, - coalesce(SUM(session_count_ssh), 0)::bigint AS session_count_ssh, - coalesce(SUM(session_count_jetbrains), 0)::bigint AS session_count_jetbrains, - coalesce(SUM(session_count_reconnecting_pty), 0)::bigint AS session_count_reconnecting_pty, + coalesce(SUM((session_counts ->> 'vscode')::bigint), 0)::bigint AS session_count_vscode, + coalesce(SUM((session_counts ->> 'ssh')::bigint), 0)::bigint AS session_count_ssh, + coalesce(SUM((session_counts ->> 'jetbrains')::bigint), 0)::bigint AS session_count_jetbrains, + coalesce(SUM((session_counts ->> 'reconnecting_pty')::bigint), 0)::bigint AS session_count_reconnecting_pty, coalesce(SUM(connection_count), 0)::bigint AS connection_count FROM workspace_agent_stats -- We only want the latest stats, but those stats might be -- spread across multiple rows. - WHERE usage = true AND created_at > now() - '1 minute'::interval + WHERE usage AND created_at > now() - '1 minute'::interval GROUP BY user_id, agent_id, workspace_id ) SELECT diff --git a/coderd/idemetadata/idemetadata.go b/coderd/idemetadata/idemetadata.go new file mode 100644 index 00000000000..1f83f137860 --- /dev/null +++ b/coderd/idemetadata/idemetadata.go @@ -0,0 +1,83 @@ +// Package idemetadata defines the app names and family grouping used in +// workspace usage reporting. It is a leaf package importable by both agent +// and coderd code. +package idemetadata + +import ( + "strings" + + utilstrings "github.com/coder/coder/v2/coderd/util/strings" +) + +// MaxAppNameLength is the maximum app name length in runes; longer names +// are truncated before storage. +const MaxAppNameLength = 64 + +// MaxSessionCountEntries bounds distinct app names per stats report; +// overflow aggregates under AppNameUnknown. +const MaxSessionCountEntries = 64 + +// Canonical app names for Coder's built-in session types. +const ( + AppNameVSCode = "vscode" + AppNameJetBrains = "jetbrains" + AppNameZed = "zed" + AppNameSSH = "ssh" + AppNameReconnectingPTY = "reconnecting_pty" + AppNameUnknown = "unknown" +) + +// families groups app names for metric labels and the Connection_Type enum, +// never for storage, so adding an alias needs no migration and a missing one +// only costs an AppNameUnknown label. Names are the ids Coder's registry +// modules use, listed ahead of the clients that will report them. +var families = map[string]string{ + AppNameVSCode: AppNameVSCode, + "vscode_insiders": AppNameVSCode, + "vscode_web": AppNameVSCode, + "code_server": AppNameVSCode, + "cursor": AppNameVSCode, + "windsurf": AppNameVSCode, + "positron": AppNameVSCode, + "vscodium": AppNameVSCode, + "codium": AppNameVSCode, + "antigravity": AppNameVSCode, + "trae": AppNameVSCode, + "kiro": AppNameVSCode, + "devin": AppNameVSCode, + + AppNameJetBrains: AppNameJetBrains, + AppNameZed: AppNameZed, + AppNameSSH: AppNameSSH, + AppNameReconnectingPTY: AppNameReconnectingPTY, +} + +// Family returns the family for the given app name. Matching is +// case-insensitive and alias-aware; unknown names map to AppNameUnknown. +func Family(appName string) string { + if family, ok := families[canonicalKey(appName)]; ok { + return family + } + return AppNameUnknown +} + +// Normalize prepares a client-supplied app name for storage: it strips null +// bytes (Postgres TEXT rejects them), truncates to MaxAppNameLength runes, +// trims whitespace, lowercases, and folds hyphens to underscores. +// +// An empty name becomes AppNameUnknown. An unset CODER_SSH_SESSION_TYPE or +// --usage-app instead means plain SSH, resolved by those callers first. +func Normalize(appName string) string { + appName = strings.ReplaceAll(appName, "\x00", "") + appName = utilstrings.Truncate(appName, MaxAppNameLength) + appName = strings.TrimSpace(appName) + if appName == "" { + return AppNameUnknown + } + return canonicalKey(appName) +} + +// canonicalKey lowercases and folds hyphens to underscores. +func canonicalKey(appName string) string { + return strings.ReplaceAll(strings.ToLower(appName), "-", "_") +} diff --git a/coderd/idemetadata/idemetadata_internal_test.go b/coderd/idemetadata/idemetadata_internal_test.go new file mode 100644 index 00000000000..ea391492c77 --- /dev/null +++ b/coderd/idemetadata/idemetadata_internal_test.go @@ -0,0 +1,16 @@ +package idemetadata + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// Lookups fold input through canonicalKey before probing, so map keys that +// are not already canonical can never match. +func TestFamilyKeysAreCanonical(t *testing.T) { + t.Parallel() + for name := range families { + require.Equal(t, canonicalKey(name), name) + } +} diff --git a/coderd/idemetadata/idemetadata_test.go b/coderd/idemetadata/idemetadata_test.go new file mode 100644 index 00000000000..7e5a97cc5bb --- /dev/null +++ b/coderd/idemetadata/idemetadata_test.go @@ -0,0 +1,80 @@ +package idemetadata_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/idemetadata" + "github.com/coder/coder/v2/codersdk" +) + +func TestNormalize(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + input string + want string + }{ + {"CanonicalPassthrough", "vscode", "vscode"}, + {"KnownNameCaseInsensitive", "JetBrains", "jetbrains"}, + {"LegacyAlias", "reconnecting-pty", "reconnecting_pty"}, + {"HyphenatedKnownName", "vscode-insiders", "vscode_insiders"}, + {"UnknownHyphensFolded", "my-future-ide", "my_future_ide"}, + {"UnknownLowercased", "Cursor Nightly", "cursor nightly"}, + {"UnknownPreservesUnicode", "エディタ", "エディタ"}, + {"StripsNullBytes", "cur\x00sor", "cursor"}, + {"TrimsWhitespace", " vscode\t", "vscode"}, + {"Empty", "", "unknown"}, + {"OnlyNullBytes", "\x00\x00", "unknown"}, + {"OnlyWhitespace", " ", "unknown"}, + {"TruncatesToMaxRunes", strings.Repeat("a", idemetadata.MaxAppNameLength+10), strings.Repeat("a", idemetadata.MaxAppNameLength)}, + {"TruncatesMultibyteSafely", strings.Repeat("あ", idemetadata.MaxAppNameLength+1), strings.Repeat("あ", idemetadata.MaxAppNameLength)}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, idemetadata.Normalize(tc.input)) + }) + } +} + +// The codersdk usage-app vocabulary is declared independently because +// codersdk cannot import coderd packages. This pins it to the canonical +// names so the two cannot drift. +func TestCodersdkUsageAppNamesAreCanonical(t *testing.T) { + t.Parallel() + + for sdkName, want := range map[codersdk.UsageAppName]string{ + codersdk.UsageAppNameVscode: idemetadata.AppNameVSCode, + codersdk.UsageAppNameJetbrains: idemetadata.AppNameJetBrains, + codersdk.UsageAppNameReconnectingPty: idemetadata.AppNameReconnectingPTY, + codersdk.UsageAppNameSSH: idemetadata.AppNameSSH, + } { + require.Equal(t, want, idemetadata.Normalize(string(sdkName))) + } +} + +func TestFamily(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + input string + want string + }{ + {"VSCode", "vscode", idemetadata.AppNameVSCode}, + {"VSCodeFork", "cursor", idemetadata.AppNameVSCode}, + {"VSCodeInsidersHyphenated", "vscode-insiders", idemetadata.AppNameVSCode}, + {"CaseInsensitive", "JetBrains", idemetadata.AppNameJetBrains}, + {"NonForkGetsOwnFamily", "zed", idemetadata.AppNameZed}, + {"Alias", "reconnecting-pty", idemetadata.AppNameReconnectingPTY}, + {"Unknown", "SomeFutureIDE", idemetadata.AppNameUnknown}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, idemetadata.Family(tc.input)) + }) + } +} diff --git a/coderd/metricscache/metricscache_test.go b/coderd/metricscache/metricscache_test.go index f730dcc2400..6a7bf5b43a3 100644 --- a/coderd/metricscache/metricscache_test.go +++ b/coderd/metricscache/metricscache_test.go @@ -3,7 +3,6 @@ package metricscache_test import ( "context" "database/sql" - "encoding/json" "sync/atomic" "testing" "time" @@ -308,28 +307,13 @@ func TestCache_DeploymentStats(t *testing.T) { DeploymentStats: time.Minute, }, false) - err := db.InsertWorkspaceAgentStats(context.Background(), database.InsertWorkspaceAgentStatsParams{ - ID: []uuid.UUID{uuid.New()}, - CreatedAt: []time.Time{clock.Now()}, - WorkspaceID: []uuid.UUID{uuid.New()}, - UserID: []uuid.UUID{uuid.New()}, - TemplateID: []uuid.UUID{uuid.New()}, - AgentID: []uuid.UUID{uuid.New()}, - ConnectionsByProto: json.RawMessage(`[{}]`), - - RxPackets: []int64{0}, - RxBytes: []int64{1}, - TxPackets: []int64{0}, - TxBytes: []int64{1}, - ConnectionCount: []int64{1}, - SessionCountVSCode: []int64{1}, - SessionCountJetBrains: []int64{0}, - SessionCountReconnectingPTY: []int64{0}, - SessionCountSSH: []int64{0}, - ConnectionMedianLatencyMS: []float64{10}, - Usage: []bool{false}, - }) - require.NoError(t, err) + dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{ + CreatedAt: clock.Now(), + RxBytes: 1, + TxBytes: 1, + ConnectionCount: 1, + ConnectionMedianLatencyMS: 10, + }, map[string]int64{"vscode": 1}) // Wait for both ticker functions to be created (template build times and deployment stats) tickerTrap.MustWait(ctx).MustRelease(ctx) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 53792cf78a9..2384b94c305 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -3324,7 +3324,7 @@ func requireGetManifest(ctx context.Context, t testing.TB, aAPI agentproto.DRPCA } func postStartup(ctx context.Context, t testing.TB, client agent.Client, startup *agentproto.Startup) error { - aAPI, _, err := client.ConnectRPC210(ctx) + aAPI, _, err := client.ConnectRPC211(ctx) require.NoError(t, err) defer func() { cErr := aAPI.DRPCConn().Close() diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 21d11d88b76..20f0efc83db 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1783,34 +1783,11 @@ func (api *API) postWorkspaceUsage(rw http.ResponseWriter, r *http.Request) { }) return } - if !slices.Contains(codersdk.AllowedAppNames, req.AppName) { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid request", - Validations: []codersdk.ValidationError{{ - Field: "app_name", - Detail: fmt.Sprintf("must be one of %v", codersdk.AllowedAppNames), - }}, - }) - return - } + // Any app name is accepted, normalized at ingestion. stat := &proto.Stats{ ConnectionCount: 1, - } - switch req.AppName { - case codersdk.UsageAppNameVscode: - stat.SessionCountVscode = 1 - case codersdk.UsageAppNameJetbrains: - stat.SessionCountJetbrains = 1 - case codersdk.UsageAppNameReconnectingPty: - stat.SessionCountReconnectingPty = 1 - case codersdk.UsageAppNameSSH: - stat.SessionCountSsh = 1 - default: - // This means the app_name is in the codersdk.AllowedAppNames but not being - // handled by this switch statement. - httpapi.InternalServerError(rw, xerrors.Errorf("unknown app_name %q", req.AppName)) - return + SessionCounts: map[string]int64{string(req.AppName): 1}, } agent, err := api.Database.GetWorkspaceAgentByID(ctx, req.AgentID) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index fdf33461af1..232c75fac0a 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -5242,12 +5242,12 @@ func TestWorkspaceUsageTracking(t *testing.T) { AppName: "ssh", }) require.ErrorContains(t, err, "app_name") - // unknown app name fails + // unknown app names are accepted err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{ AgentID: workspace.LatestBuild.Resources[0].Agents[0].ID, - AppName: "unknown", + AppName: "SomeFutureIDE", }) - require.ErrorContains(t, err, "app_name") + require.NoError(t, err) // vscode works err = client.PostWorkspaceUsageWithBody(ctx, r.Workspace.ID, codersdk.PostWorkspaceUsageRequest{ diff --git a/coderd/workspacestats/batcher.go b/coderd/workspacestats/batcher.go index 847ef562fbb..b2c52d78456 100644 --- a/coderd/workspacestats/batcher.go +++ b/coderd/workspacestats/batcher.go @@ -17,6 +17,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/idemetadata" ) const ( @@ -37,9 +38,9 @@ type DBBatcher struct { mu sync.Mutex // TODO: make this a buffered chan instead? buf *database.InsertWorkspaceAgentStatsParams - // NOTE: we batch this separately as it's a jsonb field and - // pq.Array + unnest doesn't play nicely with this. + // These objects are marshaled into positional arrays on flush. connectionsByProto []map[string]int64 + sessionCounts []map[string]int64 batchSize int // tickCh is used to periodically flush the buffer. @@ -140,6 +141,17 @@ func (b *DBBatcher) Add( st *agentproto.Stats, usage bool, ) { + // Normalize and cap outside the lock. + sessionCounts := normalizedSessionCounts(st) + if len(sessionCounts) > idemetadata.MaxSessionCountEntries { + b.log.Warn(context.Background(), "too many distinct session types, overflow counted under unknown", + slog.F("agent_id", agentID), + slog.F("reported", len(sessionCounts)), + slog.F("max", idemetadata.MaxSessionCountEntries), + ) + } + sessionCounts = capSessionCounts(sessionCounts) + b.mu.Lock() defer b.mu.Unlock() @@ -152,19 +164,14 @@ func (b *DBBatcher) Add( b.buf.TemplateID = append(b.buf.TemplateID, templateID) b.buf.WorkspaceID = append(b.buf.WorkspaceID, workspaceID) - // Store the connections by proto separately as it's a jsonb field. We marshal on flush. - // b.buf.ConnectionsByProto = append(b.buf.ConnectionsByProto, st.ConnectionsByProto) b.connectionsByProto = append(b.connectionsByProto, st.ConnectionsByProto) + b.sessionCounts = append(b.sessionCounts, sessionCounts) b.buf.ConnectionCount = append(b.buf.ConnectionCount, st.ConnectionCount) b.buf.RxPackets = append(b.buf.RxPackets, st.RxPackets) b.buf.RxBytes = append(b.buf.RxBytes, st.RxBytes) b.buf.TxPackets = append(b.buf.TxPackets, st.TxPackets) b.buf.TxBytes = append(b.buf.TxBytes, st.TxBytes) - b.buf.SessionCountVSCode = append(b.buf.SessionCountVSCode, st.SessionCountVscode) - b.buf.SessionCountJetBrains = append(b.buf.SessionCountJetBrains, st.SessionCountJetbrains) - b.buf.SessionCountReconnectingPTY = append(b.buf.SessionCountReconnectingPTY, st.SessionCountReconnectingPty) - b.buf.SessionCountSSH = append(b.buf.SessionCountSSH, st.SessionCountSsh) b.buf.ConnectionMedianLatencyMS = append(b.buf.ConnectionMedianLatencyMS, st.ConnectionMedianLatencyMs) b.buf.Usage = append(b.buf.Usage, usage) @@ -245,6 +252,14 @@ func (b *DBBatcher) flush(ctx context.Context, forced bool, reason string) { b.buf.ConnectionsByProto = payload } + sessionCountsPayload, err := json.Marshal(b.sessionCounts) + if err != nil { + b.log.Error(ctx, "unable to marshal agent session counts, dropping data", slog.Error(err)) + b.buf.SessionCounts = json.RawMessage(`[]`) + } else { + b.buf.SessionCounts = sessionCountsPayload + } + // nolint:gocritic // (#13146) Will be moved soon as part of refactor. err = b.store.InsertWorkspaceAgentStats(ctx, *b.buf) elapsed := time.Since(start) @@ -263,27 +278,25 @@ func (b *DBBatcher) flush(ctx context.Context, forced bool, reason string) { // initBuf resets the buffer. b MUST be locked. func (b *DBBatcher) initBuf(size int) { b.buf = &database.InsertWorkspaceAgentStatsParams{ - ID: make([]uuid.UUID, 0, b.batchSize), - CreatedAt: make([]time.Time, 0, b.batchSize), - UserID: make([]uuid.UUID, 0, b.batchSize), - WorkspaceID: make([]uuid.UUID, 0, b.batchSize), - TemplateID: make([]uuid.UUID, 0, b.batchSize), - AgentID: make([]uuid.UUID, 0, b.batchSize), - ConnectionsByProto: json.RawMessage("[]"), - ConnectionCount: make([]int64, 0, b.batchSize), - RxPackets: make([]int64, 0, b.batchSize), - RxBytes: make([]int64, 0, b.batchSize), - TxPackets: make([]int64, 0, b.batchSize), - TxBytes: make([]int64, 0, b.batchSize), - SessionCountVSCode: make([]int64, 0, b.batchSize), - SessionCountJetBrains: make([]int64, 0, b.batchSize), - SessionCountReconnectingPTY: make([]int64, 0, b.batchSize), - SessionCountSSH: make([]int64, 0, b.batchSize), - ConnectionMedianLatencyMS: make([]float64, 0, b.batchSize), - Usage: make([]bool, 0, b.batchSize), + ID: make([]uuid.UUID, 0, b.batchSize), + CreatedAt: make([]time.Time, 0, b.batchSize), + UserID: make([]uuid.UUID, 0, b.batchSize), + WorkspaceID: make([]uuid.UUID, 0, b.batchSize), + TemplateID: make([]uuid.UUID, 0, b.batchSize), + AgentID: make([]uuid.UUID, 0, b.batchSize), + ConnectionsByProto: json.RawMessage("[]"), + ConnectionCount: make([]int64, 0, b.batchSize), + RxPackets: make([]int64, 0, b.batchSize), + RxBytes: make([]int64, 0, b.batchSize), + TxPackets: make([]int64, 0, b.batchSize), + TxBytes: make([]int64, 0, b.batchSize), + SessionCounts: json.RawMessage("[]"), + ConnectionMedianLatencyMS: make([]float64, 0, b.batchSize), + Usage: make([]bool, 0, b.batchSize), } b.connectionsByProto = make([]map[string]int64, 0, size) + b.sessionCounts = make([]map[string]int64, 0, size) } func (b *DBBatcher) resetBuf() { @@ -299,11 +312,9 @@ func (b *DBBatcher) resetBuf() { b.buf.RxBytes = b.buf.RxBytes[:0] b.buf.TxPackets = b.buf.TxPackets[:0] b.buf.TxBytes = b.buf.TxBytes[:0] - b.buf.SessionCountVSCode = b.buf.SessionCountVSCode[:0] - b.buf.SessionCountJetBrains = b.buf.SessionCountJetBrains[:0] - b.buf.SessionCountReconnectingPTY = b.buf.SessionCountReconnectingPTY[:0] - b.buf.SessionCountSSH = b.buf.SessionCountSSH[:0] + b.buf.SessionCounts = json.RawMessage(`[]`) b.buf.ConnectionMedianLatencyMS = b.buf.ConnectionMedianLatencyMS[:0] b.buf.Usage = b.buf.Usage[:0] b.connectionsByProto = b.connectionsByProto[:0] + b.sessionCounts = b.sessionCounts[:0] } diff --git a/coderd/workspacestats/batcher_internal_test.go b/coderd/workspacestats/batcher_internal_test.go index 48983be561e..03ca89fe8d6 100644 --- a/coderd/workspacestats/batcher_internal_test.go +++ b/coderd/workspacestats/batcher_internal_test.go @@ -2,9 +2,11 @@ package workspacestats import ( "context" + "encoding/json" "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "cdr.dev/slog/v3" @@ -26,7 +28,7 @@ func TestBatchStats(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) log := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) - store, ps := dbtestutil.NewDB(t) + store, ps, sqlDB := dbtestutil.NewDBWithSQLDB(t) // Set up some test dependencies. deps1 := setupDeps(t, store, ps) @@ -59,22 +61,54 @@ func TestBatchStats(t *testing.T) { require.NoError(t, err, "should not error getting stats") require.Empty(t, stats, "should have no stats for workspace") - // Given: a single data point is added for workspace + // Given: a stat per workspace, with session counts distinct per agent so + // that a positional misalignment on insert shows up. t2 := t1.Add(time.Second) - t.Log("inserting 1 stat") - b.Add(t2.Add(time.Millisecond), deps1.Agent.ID, deps1.User.ID, deps1.Template.ID, deps1.Workspace.ID, randStats(t), false) + t.Log("inserting 2 stats") + b.Add(t2.Add(time.Millisecond), deps1.Agent.ID, deps1.Template.ID, deps1.User.ID, deps1.Workspace.ID, randStats(t, func(s *agentproto.Stats) { + s.SessionCounts = map[string]int64{"VSCode": 3, "ssh": 1, "idle-ide": 0} + }), false) + b.Add(t2.Add(time.Millisecond), deps2.Agent.ID, deps2.Template.ID, deps2.User.ID, deps2.Workspace.ID, randStats(t, func(s *agentproto.Stats) { + s.SessionCounts = map[string]int64{"jetbrains": 4, "reconnecting-pty": 2} + }), false) // When: it becomes time to report stats // Signal a tick and wait for a flush to complete. tick <- t2 f = <-flushed // Wait for a flush to complete. - require.Equal(t, 1, f, "expected one stat to be flushed") + require.Equal(t, 2, f, "expected two stats to be flushed") t.Log("flush 2 completed") - // Then: it should report a single stat. + // Then: counts reach the right agent, normalized, without the zero entry. stats, err = store.GetWorkspaceAgentStats(ctx, t2) require.NoError(t, err, "should not error getting stats") - require.Len(t, stats, 1, "should have stats for workspace") + require.Len(t, stats, 2, "should have stats for both workspaces") + byAgent := make(map[uuid.UUID]database.GetWorkspaceAgentStatsRow) + for _, stat := range stats { + byAgent[stat.AgentID] = stat + } + require.EqualValues(t, 3, byAgent[deps1.Agent.ID].SessionCountVSCode) + require.EqualValues(t, 1, byAgent[deps1.Agent.ID].SessionCountSSH) + require.EqualValues(t, 0, byAgent[deps1.Agent.ID].SessionCountJetBrains) + require.EqualValues(t, 4, byAgent[deps2.Agent.ID].SessionCountJetBrains) + require.EqualValues(t, 2, byAgent[deps2.Agent.ID].SessionCountReconnectingPTY) + require.EqualValues(t, 0, byAgent[deps2.Agent.ID].SessionCountVSCode) + + // Each row stores its normalized session counts. + storedSessionCounts := func(agentID uuid.UUID) map[string]int64 { + var payload json.RawMessage + require.NoError(t, sqlDB.QueryRowContext(ctx, ` + SELECT session_counts + FROM workspace_agent_stats + WHERE agent_id = $1 AND created_at > $2 + `, agentID, t2).Scan(&payload)) + + var counts map[string]int64 + require.NoError(t, json.Unmarshal(payload, &counts)) + return counts + } + require.Equal(t, map[string]int64{"ssh": 1, "vscode": 3}, storedSessionCounts(deps1.Agent.ID)) + require.Equal(t, map[string]int64{"jetbrains": 4, "reconnecting_pty": 2}, storedSessionCounts(deps2.Agent.ID)) // Given: a lot of data points are added for both workspaces // (equal to batch size) @@ -86,9 +120,9 @@ func TestBatchStats(t *testing.T) { t.Logf("inserting %d stats", defaultBufferSize) for i := 0; i < defaultBufferSize; i++ { if i%2 == 0 { - b.Add(t3.Add(time.Millisecond), deps1.Agent.ID, deps1.User.ID, deps1.Template.ID, deps1.Workspace.ID, randStats(t), false) + b.Add(t3.Add(time.Millisecond), deps1.Agent.ID, deps1.Template.ID, deps1.User.ID, deps1.Workspace.ID, randStats(t), false) } else { - b.Add(t3.Add(time.Millisecond), deps2.Agent.ID, deps2.User.ID, deps2.Template.ID, deps2.Workspace.ID, randStats(t), false) + b.Add(t3.Add(time.Millisecond), deps2.Agent.ID, deps2.Template.ID, deps2.User.ID, deps2.Workspace.ID, randStats(t), false) } } }() diff --git a/coderd/workspacestats/reporter.go b/coderd/workspacestats/reporter.go index c5b8f9f70ad..7705f50c742 100644 --- a/coderd/workspacestats/reporter.go +++ b/coderd/workspacestats/reporter.go @@ -154,10 +154,7 @@ func (r *Reporter) ReportAgentStats(ctx context.Context, now time.Time, workspac } // workspace activity: if no sessions we do not bump activity - if usage && stats.SessionCountVscode == 0 && - stats.SessionCountJetbrains == 0 && - stats.SessionCountReconnectingPty == 0 && - stats.SessionCountSsh == 0 { + if usage && !HasSessionCounts(stats) { return nil } diff --git a/coderd/workspacestats/sessioncounts.go b/coderd/workspacestats/sessioncounts.go new file mode 100644 index 00000000000..44a2f4c0a5a --- /dev/null +++ b/coderd/workspacestats/sessioncounts.go @@ -0,0 +1,93 @@ +package workspacestats + +import ( + "cmp" + "maps" + "slices" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/idemetadata" +) + +// normalizedSessionCounts returns an agent's per-app session counts, +// normalized, with non-positive counts dropped. One agent fills either the map +// (API >= 2.11) or the deprecated fields, so those are a fallback. +func normalizedSessionCounts(st *agentproto.Stats) map[string]int64 { + counts := make(map[string]int64, len(st.GetSessionCounts())) + for app, count := range st.GetSessionCounts() { + if count > 0 { + counts[idemetadata.Normalize(app)] += count + } + } + if len(counts) == 0 { + //nolint:staticcheck // Deprecated fields are read for old-agent compatibility. + for _, d := range [...]struct { + app string + count int64 + }{ + {idemetadata.AppNameVSCode, st.SessionCountVscode}, + {idemetadata.AppNameJetBrains, st.SessionCountJetbrains}, + {idemetadata.AppNameReconnectingPTY, st.SessionCountReconnectingPty}, + {idemetadata.AppNameSSH, st.SessionCountSsh}, + } { + if d.count > 0 { + counts[d.app] = d.count + } + } + } + return counts +} + +// capSessionCounts keeps at most idemetadata.MaxSessionCountEntries entries and +// sums the rest into AppNameUnknown, which can add one past the cap. Known +// families always survive; the rest fill the room busiest first. +func capSessionCounts(counts map[string]int64) map[string]int64 { + if len(counts) <= idemetadata.MaxSessionCountEntries { + return counts + } + capped := make(map[string]int64, idemetadata.MaxSessionCountEntries+1) + for name, count := range counts { + if idemetadata.Family(name) != idemetadata.AppNameUnknown { + capped[name] = count + } + } + byCount := slices.SortedFunc(maps.Keys(counts), func(a, b string) int { + return cmp.Or(cmp.Compare(counts[b], counts[a]), cmp.Compare(a, b)) + }) + var overflow int64 + for _, name := range byCount { + if _, kept := capped[name]; kept { + continue + } + if len(capped) < idemetadata.MaxSessionCountEntries { + capped[name] = counts[name] + continue + } + overflow += counts[name] + } + if overflow > 0 { + capped[idemetadata.AppNameUnknown] += overflow + } + return capped +} + +// HasSessionCounts reports whether the stats contain any active session, +// including the deprecated fields. Normalizing cannot change a count's sign. +func HasSessionCounts(st *agentproto.Stats) bool { + for _, count := range st.GetSessionCounts() { + if count > 0 { + return true + } + } + //nolint:staticcheck // Deprecated fields are read for old-agent compatibility. + return st.SessionCountVscode > 0 || st.SessionCountJetbrains > 0 || + st.SessionCountReconnectingPty > 0 || st.SessionCountSsh > 0 +} + +// ClearSessionCounts zeroes all session counts on the given stats, including +// the deprecated fields sent by older agents. +func ClearSessionCounts(st *agentproto.Stats) { + st.SessionCounts = nil + //nolint:staticcheck // Deprecated fields are cleared for old-agent compatibility. + st.SessionCountSsh, st.SessionCountJetbrains, st.SessionCountVscode, st.SessionCountReconnectingPty = 0, 0, 0, 0 +} diff --git a/coderd/workspacestats/sessioncounts_internal_test.go b/coderd/workspacestats/sessioncounts_internal_test.go new file mode 100644 index 00000000000..732a38aad42 --- /dev/null +++ b/coderd/workspacestats/sessioncounts_internal_test.go @@ -0,0 +1,125 @@ +package workspacestats + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + agentproto "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/idemetadata" +) + +// sessionCountsFromProto is what the batcher applies on ingest. +func sessionCountsFromProto(st *agentproto.Stats) map[string]int64 { + return capSessionCounts(normalizedSessionCounts(st)) +} + +func TestSessionCountsFromProto(t *testing.T) { + t.Parallel() + + //nolint:staticcheck // Deprecated fields simulate old agents. + for _, tc := range []struct { + name string + stats *agentproto.Stats + want map[string]int64 + }{ + { + "Empty", + &agentproto.Stats{}, + map[string]int64{}, + }, + { + "NormalizesAndMergesNames", + &agentproto.Stats{SessionCounts: map[string]int64{"VSCode": 1, "vscode": 2, "Reconnecting-PTY": 1}}, + map[string]int64{"vscode": 3, "reconnecting_pty": 1}, + }, + { + "DropsNonPositiveEntries", + &agentproto.Stats{SessionCounts: map[string]int64{"vscode": 1, "reconnecting_pty": 0, "bogus": -1}}, + map[string]int64{"vscode": 1}, + }, + { + "MapTakesPrecedenceOverDeprecatedFields", + &agentproto.Stats{SessionCounts: map[string]int64{"cursor": 2, "ssh": 1}, SessionCountVscode: 9}, + map[string]int64{"cursor": 2, "ssh": 1}, + }, + { + "OldAgentFallback", + &agentproto.Stats{SessionCountVscode: 3, SessionCountJetbrains: 1, SessionCountSsh: 2}, + map[string]int64{"vscode": 3, "jetbrains": 1, "ssh": 2}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, sessionCountsFromProto(tc.stats)) + }) + } +} + +func TestCapSessionCounts(t *testing.T) { + t.Parallel() + + // The well-known names carry the lowest counts and the busiest name sorts + // last, so surviving the cap can only come from the ordering rules. + counts := map[string]int64{idemetadata.AppNameSSH: 1, idemetadata.AppNameVSCode: 1, "zzz-busy-ide": 5} + const overflowing = 200 + for i := range overflowing { + counts[fmt.Sprintf("0ide-%03d", i)] = 2 + } + + got := sessionCountsFromProto(&agentproto.Stats{SessionCounts: counts}) + + require.Len(t, got, idemetadata.MaxSessionCountEntries+1, "cap plus the unknown bucket") + require.EqualValues(t, 1, got[idemetadata.AppNameSSH]) + require.EqualValues(t, 1, got[idemetadata.AppNameVSCode]) + require.EqualValues(t, 5, got["zzz_busy_ide"]) + // Those three plus 61 more fill the cap; the other 139 sum into unknown. + require.EqualValues(t, 2, got["0ide_000"]) + require.NotContains(t, got, "0ide_199") + require.EqualValues(t, (overflowing-61)*2, got[idemetadata.AppNameUnknown]) + + var reported, stored int64 + for _, count := range counts { + reported += count + } + for _, count := range got { + stored += count + } + require.Equal(t, reported, stored, "capping must not lose counts") +} + +func TestHasSessionCounts(t *testing.T) { + t.Parallel() + + //nolint:staticcheck // Deprecated fields simulate old agents. + for _, tc := range []struct { + name string + stats *agentproto.Stats + want bool + }{ + {"Empty", &agentproto.Stats{}, false}, + {"MapPositive", &agentproto.Stats{SessionCounts: map[string]int64{"cursor": 1}}, true}, + {"MapZeroOnly", &agentproto.Stats{SessionCounts: map[string]int64{"ssh": 0}}, false}, + {"DeprecatedFieldPositive", &agentproto.Stats{SessionCountSsh: 2}, true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, HasSessionCounts(tc.stats)) + }) + } +} + +func TestClearSessionCounts(t *testing.T) { + t.Parallel() + + //nolint:staticcheck // Deprecated fields simulate an old agent. + st := &agentproto.Stats{ + SessionCounts: map[string]int64{"vscode": 1}, + SessionCountVscode: 1, + SessionCountSsh: 2, + } + ClearSessionCounts(st) + require.Empty(t, sessionCountsFromProto(st)) + require.False(t, HasSessionCounts(st)) +} diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 5d1f6c4a6d9..d5b3a4aee75 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -349,13 +349,26 @@ func (c *Client) ConnectRPC210(ctx context.Context) ( return proto.NewDRPCAgentClient(conn), tailnetproto.NewDRPCTailnetClient(conn), nil } -// ConnectRPC210WithRole is like ConnectRPC210 but sends an explicit role +// ConnectRPC211 returns a dRPC client to the Agent API v2.11. It is useful +// when you want to report per-app session counts via the session_counts +// map on Stats. +func (c *Client) ConnectRPC211(ctx context.Context) ( + proto.DRPCAgentClient211, tailnetproto.DRPCTailnetClient28, error, +) { + conn, err := c.connectRPCVersion(ctx, apiversion.New(2, 11), "") + if err != nil { + return nil, nil, err + } + return proto.NewDRPCAgentClient(conn), tailnetproto.NewDRPCTailnetClient(conn), nil +} + +// ConnectRPC211WithRole is like ConnectRPC211 but sends an explicit role // query parameter to the server. Use "agent" for workspace agents to // enable connection monitoring. -func (c *Client) ConnectRPC210WithRole(ctx context.Context, role string) ( - proto.DRPCAgentClient210, tailnetproto.DRPCTailnetClient28, error, +func (c *Client) ConnectRPC211WithRole(ctx context.Context, role string) ( + proto.DRPCAgentClient211, tailnetproto.DRPCTailnetClient28, error, ) { - conn, err := c.connectRPCVersion(ctx, apiversion.New(2, 10), role) + conn, err := c.connectRPCVersion(ctx, apiversion.New(2, 11), role) if err != nil { return nil, nil, err } @@ -568,45 +581,6 @@ func WithFixedToken(token string) SessionTokenSetup { } } -// Stats records the Agent's network connection statistics for use in -// user-facing metrics and debugging. -type Stats struct { - // ConnectionsByProto is a count of connections by protocol. - ConnectionsByProto map[string]int64 `json:"connections_by_proto"` - // ConnectionCount is the number of connections received by an agent. - ConnectionCount int64 `json:"connection_count"` - // ConnectionMedianLatencyMS is the median latency of all connections in milliseconds. - ConnectionMedianLatencyMS float64 `json:"connection_median_latency_ms"` - // RxPackets is the number of received packets. - RxPackets int64 `json:"rx_packets"` - // RxBytes is the number of received bytes. - RxBytes int64 `json:"rx_bytes"` - // TxPackets is the number of transmitted bytes. - TxPackets int64 `json:"tx_packets"` - // TxBytes is the number of transmitted bytes. - TxBytes int64 `json:"tx_bytes"` - - // SessionCountVSCode is the number of connections received by an agent - // that are from our VS Code extension. - SessionCountVSCode int64 `json:"session_count_vscode"` - // SessionCountJetBrains is the number of connections received by an agent - // that are from our JetBrains extension. - SessionCountJetBrains int64 `json:"session_count_jetbrains"` - // SessionCountReconnectingPTY is the number of connections received by an agent - // that are from the reconnecting web terminal. - SessionCountReconnectingPTY int64 `json:"session_count_reconnecting_pty"` - // SessionCountSSH is the number of connections received by an agent - // that are normal, non-tagged SSH sessions. - SessionCountSSH int64 `json:"session_count_ssh"` - - // Metrics collected by the agent - Metrics []AgentMetric `json:"metrics"` -} - -func (s Stats) SessionCount() int64 { - return s.SessionCountVSCode + s.SessionCountJetBrains + s.SessionCountReconnectingPTY + s.SessionCountSSH -} - type AgentMetricType string const ( diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 6a78ecd7b36..8b1411d22db 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -383,6 +383,10 @@ type PostWorkspaceUsageRequest struct { type UsageAppName string +// Well-known usage app names. The workspace usage API accepts any app name; +// unrecognized names are normalized at ingestion. These values are wire +// format and cannot change: note the hyphen in reconnecting-pty, which +// normalization folds to the canonical reconnecting_pty. const ( UsageAppNameVscode UsageAppName = "vscode" UsageAppNameJetbrains UsageAppName = "jetbrains" @@ -390,13 +394,6 @@ const ( UsageAppNameSSH UsageAppName = "ssh" ) -var AllowedAppNames = []UsageAppName{ - UsageAppNameVscode, - UsageAppNameJetbrains, - UsageAppNameReconnectingPty, - UsageAppNameSSH, -} - // PostWorkspaceUsage marks the workspace as having been used recently and records an app stat. func (c *Client) PostWorkspaceUsageWithBody(ctx context.Context, id uuid.UUID, req PostWorkspaceUsageRequest) error { path := fmt.Sprintf("/api/v2/workspaces/%s/usage", id.String()) diff --git a/tailnet/proto/version.go b/tailnet/proto/version.go index 2cd8c987f19..b2958108b31 100644 --- a/tailnet/proto/version.go +++ b/tailnet/proto/version.go @@ -81,9 +81,13 @@ import ( // deployments remain interoperable. Real persistence, // KindMCPServer provider, and chatd hydration land in // CODAGT-569. +// +// API v2.11: +// - Added the session_counts map field to Stats on the Agent API, +// deprecating the fixed session_count_* fields. const ( CurrentMajor = 2 - CurrentMinor = 10 + CurrentMinor = 11 ) var CurrentVersion = apiversion.New(CurrentMajor, CurrentMinor)