mirror of https://github.com/docker/compose.git
Merge pull request #624 from docker/feat-api-metrics
Add interceptor for API metrics
This commit is contained in:
commit
e904c71b04
|
@ -21,6 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
||||
"github.com/docker/compose-cli/api/containers"
|
||||
)
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/api/containers"
|
||||
"github.com/docker/compose-cli/api/secrets"
|
||||
|
|
|
@ -19,6 +19,8 @@ package local
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/api/containers"
|
||||
"github.com/docker/compose-cli/api/secrets"
|
||||
|
@ -26,7 +28,6 @@ import (
|
|||
"github.com/docker/compose-cli/backend"
|
||||
"github.com/docker/compose-cli/context/cloud"
|
||||
"github.com/docker/compose-cli/context/store"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
const backendType = store.EcsLocalSimulationContextType
|
||||
|
|
|
@ -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
|
||||
|
@ -56,10 +64,24 @@ func NewClient() Client {
|
|||
}
|
||||
|
||||
func (c *client) Send(command Command) {
|
||||
req, err := json.Marshal(command)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wasIn := make(chan bool)
|
||||
|
||||
// Fire and forget, we don't want to slow down the user waiting for DD
|
||||
// metrics endpoint to respond. We could lose some events but that's ok.
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
|
||||
wasIn <- true
|
||||
|
||||
req, err := json.Marshal(command)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = c.httpClient.Post("http://localhost/usage", "application/json", bytes.NewBuffer(req))
|
||||
}()
|
||||
<-wasIn
|
||||
|
||||
_, _ = c.httpClient.Post("http://localhost/usage", "application/json", bytes.NewBuffer(req))
|
||||
}
|
||||
|
|
|
@ -78,27 +78,15 @@ const (
|
|||
|
||||
// Track sends the tracking analytics to Docker Desktop
|
||||
func Track(context string, args []string, flags *flag.FlagSet) {
|
||||
wasIn := make(chan bool)
|
||||
|
||||
// Fire and forget, we don't want to slow down the user waiting for DD
|
||||
// metrics endpoint to respond. We could lose some events but that's ok.
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
|
||||
wasIn <- true
|
||||
|
||||
command := getCommand(args, flags)
|
||||
if command != "" {
|
||||
c := NewClient()
|
||||
c.Send(Command{
|
||||
Command: command,
|
||||
Context: context,
|
||||
})
|
||||
}
|
||||
}()
|
||||
<-wasIn
|
||||
command := getCommand(args, flags)
|
||||
if command != "" {
|
||||
c := NewClient()
|
||||
c.Send(Command{
|
||||
Command: command,
|
||||
Context: context,
|
||||
Source: CLISource,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getCommand(args []string, flags *flag.FlagSet) string {
|
||||
|
|
|
@ -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,59 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
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