mirror of https://github.com/docker/compose.git
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 {
|
||||
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
|
||||
|
|
|
@ -95,6 +95,7 @@ func Track(context string, args []string, flags *flag.FlagSet) {
|
|||
c.Send(Command{
|
||||
Command: command,
|
||||
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.
|
||||
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()
|
||||
|
|
Loading…
Reference in New Issue