Add interceptor for API metrics, ensure registered methods have a corresponding method set for metrics
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
This commit is contained in:
parent
6cb19ed26d
commit
2570ebec86
|
|
@ -32,8 +32,16 @@ type client struct {
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Context string `json:"context"`
|
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
|
// Client sends metrics to Docker Desktopn
|
||||||
type Client interface {
|
type Client interface {
|
||||||
// Send sends the command to Docker Desktop. Note that the function doesn't
|
// Send sends the command to Docker Desktop. Note that the function doesn't
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ func Track(context string, args []string, flags *flag.FlagSet) {
|
||||||
c.Send(Command{
|
c.Send(Command{
|
||||||
Command: command,
|
Command: command,
|
||||||
Context: context,
|
Context: context,
|
||||||
|
Source: CLISource,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,10 @@ import (
|
||||||
// New returns a new GRPC server.
|
// New returns a new GRPC server.
|
||||||
func New(ctx context.Context) *grpc.Server {
|
func New(ctx context.Context) *grpc.Server {
|
||||||
s := grpc.NewServer(
|
s := grpc.NewServer(
|
||||||
grpc.UnaryInterceptor(unaryServerInterceptor(ctx)),
|
grpc.ChainUnaryInterceptor(
|
||||||
|
unaryServerInterceptor(ctx),
|
||||||
|
metricsServerInterceptor(ctx),
|
||||||
|
),
|
||||||
grpc.StreamInterceptor(streamServerInterceptor(ctx)),
|
grpc.StreamInterceptor(streamServerInterceptor(ctx)),
|
||||||
)
|
)
|
||||||
hs := health.NewServer()
|
hs := health.NewServer()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue