Skip to content

Commit

Permalink
Add interceptor for API metrics, ensure registered methods have a cor…
Browse files Browse the repository at this point in the history
…responding method set for metrics

Signed-off-by: Djordje Lukic <[email protected]>
  • Loading branch information
rumpl authored and gtardif committed Sep 15, 2020
1 parent 6cb19ed commit 2570ebe
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 1 deletion.
8 changes: 8 additions & 0 deletions metrics/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,16 @@ type client struct {
type Command struct {
Command string `json:"command"`
Context string `json:"context"`
Source string `json:"source"`
}

const (
// CLISource is sent for cli metrics
CLISource = "cli"
// APISource is sent for API metrics
APISource = "api"
)

// Client sends metrics to Docker Desktopn
type Client interface {
// Send sends the command to Docker Desktop. Note that the function doesn't
Expand Down
1 change: 1 addition & 0 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func Track(context string, args []string, flags *flag.FlagSet) {
c.Send(Command{
Command: command,
Context: context,
Source: CLISource,
})
}
}()
Expand Down
67 changes: 67 additions & 0 deletions server/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2020 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package server

import (
"context"

"google.golang.org/grpc"

"github.com/docker/compose-cli/metrics"
)

var (
methodMapping = map[string]string{
"/com.docker.api.protos.containers.v1.Containers/List": "ps",
"/com.docker.api.protos.containers.v1.Containers/Start": "start",
"/com.docker.api.protos.containers.v1.Containers/Stop": "stop",
"/com.docker.api.protos.containers.v1.Containers/Run": "run",
"/com.docker.api.protos.containers.v1.Containers/Exec": "exec",
"/com.docker.api.protos.containers.v1.Containers/Delete": "rm",
"/com.docker.api.protos.containers.v1.Containers/Kill": "kill",
"/com.docker.api.protos.containers.v1.Containers/Inspect": "inspect",
"/com.docker.api.protos.containers.v1.Containers/Logs": "logs",
"/com.docker.api.protos.streams.v1.Streaming/NewStream": "",
"/com.docker.api.protos.context.v1.Contexts/List": "context ls",
"/com.docker.api.protos.context.v1.Contexts/SetCurrent": "context use",
}
)

func metricsServerInterceptor(clictx context.Context) grpc.UnaryServerInterceptor {
client := metrics.NewClient()

return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
currentContext, err := getIncomingContext(ctx)
if err != nil {
currentContext, err = getConfigContext(clictx)
if err != nil {
return nil, err
}
}

command := methodMapping[info.FullMethod]
if command != "" {
client.Send(metrics.Command{
Command: command,
Context: currentContext,
Source: metrics.APISource,
})
}

return handler(ctx, req)
}
}
60 changes: 60 additions & 0 deletions server/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2020 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package server

import (
"context"
"strings"
"testing"

"gotest.tools/v3/assert"

"google.golang.org/grpc"

containersv1 "github.com/docker/compose-cli/protos/containers/v1"
contextsv1 "github.com/docker/compose-cli/protos/contexts/v1"
streamsv1 "github.com/docker/compose-cli/protos/streams/v1"
"github.com/docker/compose-cli/server/proxy"
)

func TestAllMethodsHaveCorrespondingCliCommand(t *testing.T) {
s := setupServer()
i := s.GetServiceInfo()
for k, v := range i {
if k == "grpc.health.v1.Health" {
continue
}
var errs []string
for _, m := range v.Methods {
name := "/" + k + "/" + m.Name
if _, keyExists := methodMapping[name]; !keyExists {
errs = append(errs, name+" not mapped to a corresponding cli command")
}
}
assert.Equal(t, "", strings.Join(errs, "\n"))
}
}

func setupServer() *grpc.Server {
ctx := context.TODO()
s := New(ctx)
p := proxy.New(ctx)
containersv1.RegisterContainersServer(s, p)
streamsv1.RegisterStreamingServer(s, p)
contextsv1.RegisterContextsServer(s, p.ContextsProxy())
return s
}
5 changes: 4 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import (
// New returns a new GRPC server.
func New(ctx context.Context) *grpc.Server {
s := grpc.NewServer(
grpc.UnaryInterceptor(unaryServerInterceptor(ctx)),
grpc.ChainUnaryInterceptor(
unaryServerInterceptor(ctx),
metricsServerInterceptor(ctx),
),
grpc.StreamInterceptor(streamServerInterceptor(ctx)),
)
hs := health.NewServer()
Expand Down

0 comments on commit 2570ebe

Please sign in to comment.