From 24b9baec1f723c9cbbfeb85841530fd86e261d6f Mon Sep 17 00:00:00 2001 From: Victor Vrantchan Date: Wed, 28 Sep 2016 07:35:15 -0400 Subject: [PATCH] add prometheus endpoint (#236) generate metrics for Users, Appconfig and Session services --- cli/serve.go | 18 +++++ glide.lock | 31 +++++++- glide.yaml | 4 + server/kolide/app.go | 8 +- server/kolide/sessions.go | 16 ++-- server/kolide/users.go | 14 ++-- server/service/metrics.go | 26 ++++++ server/service/metrics_appconfig.go | 51 ++++++++++++ server/service/metrics_sessions.go | 118 ++++++++++++++++++++++++++++ server/service/metrics_users.go | 114 +++++++++++++++++++++++++++ server/service/service_appconfig.go | 3 +- 11 files changed, 380 insertions(+), 23 deletions(-) create mode 100644 server/service/metrics.go create mode 100644 server/service/metrics_appconfig.go create mode 100644 server/service/metrics_sessions.go create mode 100644 server/service/metrics_users.go diff --git a/cli/serve.go b/cli/serve.go index 91e863c44b..345e7146d8 100644 --- a/cli/serve.go +++ b/cli/serve.go @@ -10,12 +10,14 @@ import ( "github.com/WatchBeam/clock" kitlog "github.com/go-kit/kit/log" + kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/kolide/kolide-ose/server/config" "github.com/kolide/kolide-ose/server/datastore" "github.com/kolide/kolide-ose/server/kolide" "github.com/kolide/kolide-ose/server/mail" "github.com/kolide/kolide-ose/server/service" "github.com/kolide/kolide-ose/server/version" + "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -128,14 +130,30 @@ the way that the kolide server works. } } + fieldKeys := []string{"method", "error"} + requestCount := kitprometheus.NewCounterFrom(prometheus.CounterOpts{ + Namespace: "api", + Subsystem: "service", + Name: "request_count", + Help: "Number of requests received.", + }, fieldKeys) + requestLatency := kitprometheus.NewSummaryFrom(prometheus.SummaryOpts{ + Namespace: "api", + Subsystem: "service", + Name: "request_latency_microseconds", + Help: "Total duration of requests in microseconds.", + }, fieldKeys) + svcLogger := kitlog.NewContext(logger).With("component", "service") svc = service.NewLoggingService(svc, svcLogger) + svc = service.NewMetricsService(svc, requestCount, requestLatency) httpLogger := kitlog.NewContext(logger).With("component", "http") apiHandler := service.MakeHandler(ctx, svc, config.Auth.JwtKey, httpLogger) http.Handle("/api/", accessControl(apiHandler)) http.Handle("/version", version.Handler()) + http.Handle("/metrics", prometheus.Handler()) http.Handle("/assets/", service.ServeStaticAssets("/assets/")) http.Handle("/", service.ServeFrontend()) diff --git a/glide.lock b/glide.lock index 4224ebaf85..915ccf5d09 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 3bf651074168b7611aabf854ebba43d0a41dd3cb3fbd0892268f9163e4240f9f -updated: 2016-09-20T11:30:44.994118865-07:00 +hash: 0ed5871e7b062bd45449c789a58a5b8d2df91c275af984b344b109b232ca1bb3 +updated: 2016-09-24T21:21:01.057881898-04:00 imports: - name: github.com/alecthomas/template version: a0175ee3bccc567396460bf5acd36800cb10c49c @@ -7,6 +7,10 @@ imports: - parse - name: github.com/alecthomas/units version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a +- name: github.com/beorn7/perks + version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 + subpackages: + - quantile - name: github.com/davecgh/go-spew version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d subpackages: @@ -35,6 +39,9 @@ imports: subpackages: - endpoint - log + - metrics + - metrics/internal/lv + - metrics/prometheus - transport/http - name: github.com/go-logfmt/logfmt version: d4327190ff838312623b09bfeb50d7c93c8d9c1d @@ -84,6 +91,10 @@ imports: version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d - name: github.com/mattn/go-sqlite3 version: e118d4451349065b8e7ce0f0af32e033995363f8 +- name: github.com/matttproud/golang_protobuf_extensions + version: c12348ce28de40eed0136aa2b644d0ee0650e56c + subpackages: + - pbutil - name: github.com/mitchellh/mapstructure version: ca63d7c062ee3c9f34db231e352b60012b4fd0c1 - name: github.com/pelletier/go-buffruneio @@ -98,6 +109,22 @@ imports: version: 792786c7400a136282c1664665ae0a8db921c6c2 subpackages: - difflib +- name: github.com/prometheus/client_golang + version: c5b7fccd204277076155f10851dad72b76a49317 + subpackages: + - prometheus +- name: github.com/prometheus/client_model + version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6 + subpackages: + - go +- name: github.com/prometheus/common + version: 9a94032291f2192936512bab367bc45e77990d6a + subpackages: + - expfmt + - internal/bitbucket.org/ww/goautoneg + - model +- name: github.com/prometheus/procfs + version: abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 - name: github.com/Sirupsen/logrus version: a283a10442df8dc09befd873fab202bf8a253d6a - name: github.com/spf13/afero diff --git a/glide.yaml b/glide.yaml index adb56436b5..2549d461aa 100644 --- a/glide.yaml +++ b/glide.yaml @@ -79,3 +79,7 @@ import: version: v2.0 - package: github.com/golang/mock - package: github.com/WatchBeam/clock +- package: github.com/prometheus/client_golang + version: ~0.8.0 + subpackages: + - prometheus diff --git a/server/kolide/app.go b/server/kolide/app.go index 8b2df391a5..7f40839cd9 100644 --- a/server/kolide/app.go +++ b/server/kolide/app.go @@ -1,6 +1,6 @@ package kolide -import "context" +import "golang.org/x/net/context" // AppConfigStore contains method for saving and retrieving // application configuration @@ -13,9 +13,9 @@ type AppConfigStore interface { // AppConfigService provides methods for configuring // the Kolide application type AppConfigService interface { - NewOrgInfo(ctx context.Context, p OrgInfoPayload) (*OrgInfo, error) - OrgInfo(ctx context.Context) (*OrgInfo, error) - ModifyOrgInfo(ctx context.Context, p OrgInfoPayload) (*OrgInfo, error) + NewOrgInfo(ctx context.Context, p OrgInfoPayload) (info *OrgInfo, err error) + OrgInfo(ctx context.Context) (info *OrgInfo, err error) + ModifyOrgInfo(ctx context.Context, p OrgInfoPayload) (info *OrgInfo, err error) } // OrgInfo holds information about the current diff --git a/server/kolide/sessions.go b/server/kolide/sessions.go index f0d84cf45e..30ae38299a 100644 --- a/server/kolide/sessions.go +++ b/server/kolide/sessions.go @@ -56,14 +56,14 @@ type SessionStore interface { } type SessionService interface { - Login(ctx context.Context, username, password string) (*User, string, error) - Logout(ctx context.Context) error - DestroySession(ctx context.Context) error - GetInfoAboutSessionsForUser(ctx context.Context, id uint) ([]*Session, error) - DeleteSessionsForUser(ctx context.Context, id uint) error - GetInfoAboutSession(ctx context.Context, id uint) (*Session, error) - GetSessionByKey(ctx context.Context, key string) (*Session, error) - DeleteSession(ctx context.Context, id uint) error + Login(ctx context.Context, username, password string) (user *User, token string, err error) + Logout(ctx context.Context) (err error) + DestroySession(ctx context.Context) (err error) + GetInfoAboutSessionsForUser(ctx context.Context, id uint) (sessions []*Session, err error) + DeleteSessionsForUser(ctx context.Context, id uint) (err error) + GetInfoAboutSession(ctx context.Context, id uint) (session *Session, err error) + GetSessionByKey(ctx context.Context, key string) (session *Session, err error) + DeleteSession(ctx context.Context, id uint) (err error) } // Session is the model object which represents what an active session is diff --git a/server/kolide/users.go b/server/kolide/users.go index b6e466bcba..b231579235 100644 --- a/server/kolide/users.go +++ b/server/kolide/users.go @@ -21,30 +21,30 @@ type UserStore interface { // UserService contains methods for managing a Kolide User type UserService interface { // NewUser creates a new User from a request Payload - NewUser(ctx context.Context, p UserPayload) (*User, error) + NewUser(ctx context.Context, p UserPayload) (user *User, err error) // User returns a valid User given a User ID - User(ctx context.Context, id uint) (*User, error) + User(ctx context.Context, id uint) (user *User, err error) // AuthenticatedUser returns the current user // from the viewer context - AuthenticatedUser(ctx context.Context) (*User, error) + AuthenticatedUser(ctx context.Context) (user *User, err error) // Users returns all users - Users(ctx context.Context) ([]*User, error) + Users(ctx context.Context) (users []*User, err error) // RequestPasswordReset generates a password reset request for // a user. The request results in a token emailed to the user. // If the person making the request is an admin the AdminForcedPasswordReset // parameter is enabled instead of sending an email with a password reset token - RequestPasswordReset(ctx context.Context, email string) error + RequestPasswordReset(ctx context.Context, email string) (err error) // ResetPassword validate a password reset token and updates // a user's password - ResetPassword(ctx context.Context, token, password string) error + ResetPassword(ctx context.Context, token, password string) (err error) // ModifyUser updates a user's parameters given a UserPayload - ModifyUser(ctx context.Context, userID uint, p UserPayload) (*User, error) + ModifyUser(ctx context.Context, userID uint, p UserPayload) (user *User, err error) } // User is the model struct which represents a kolide user diff --git a/server/service/metrics.go b/server/service/metrics.go new file mode 100644 index 0000000000..4f6fc0c2d1 --- /dev/null +++ b/server/service/metrics.go @@ -0,0 +1,26 @@ +package service + +import ( + "github.com/go-kit/kit/metrics" + "github.com/kolide/kolide-ose/server/kolide" +) + +type metricsMiddleware struct { + kolide.Service + requestCount metrics.Counter + requestLatency metrics.Histogram +} + +// NewMetrics service takes an existing service and wraps it +// with instrumentation middleware. +func NewMetricsService( + svc kolide.Service, + requestCount metrics.Counter, + requestLatency metrics.Histogram, +) kolide.Service { + return metricsMiddleware{ + Service: svc, + requestCount: requestCount, + requestLatency: requestLatency, + } +} diff --git a/server/service/metrics_appconfig.go b/server/service/metrics_appconfig.go new file mode 100644 index 0000000000..3c37a169ee --- /dev/null +++ b/server/service/metrics_appconfig.go @@ -0,0 +1,51 @@ +package service + +import ( + "fmt" + "time" + + "github.com/kolide/kolide-ose/server/kolide" + "golang.org/x/net/context" +) + +func (mw metricsMiddleware) NewOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) { + var ( + info *kolide.OrgInfo + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "NewOrgInfo", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + info, err = mw.Service.NewOrgInfo(ctx, p) + return info, err +} + +func (mw metricsMiddleware) OrgInfo(ctx context.Context) (*kolide.OrgInfo, error) { + var ( + info *kolide.OrgInfo + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "OrgInfo", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + info, err = mw.Service.OrgInfo(ctx) + return info, err +} + +func (mw metricsMiddleware) ModifyOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) { + var ( + info *kolide.OrgInfo + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "ModifyOrgInfo", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + info, err = mw.Service.ModifyOrgInfo(ctx, p) + return info, err +} diff --git a/server/service/metrics_sessions.go b/server/service/metrics_sessions.go new file mode 100644 index 0000000000..32b688b05e --- /dev/null +++ b/server/service/metrics_sessions.go @@ -0,0 +1,118 @@ +package service + +import ( + "fmt" + "time" + + "github.com/kolide/kolide-ose/server/kolide" + "golang.org/x/net/context" +) + +func (mw metricsMiddleware) Login(ctx context.Context, username string, password string) (*kolide.User, string, error) { + var ( + user *kolide.User + token string + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "Login", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + user, token, err = mw.Service.Login(ctx, username, password) + return user, token, err +} + +func (mw metricsMiddleware) Logout(ctx context.Context) error { + var ( + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "Logout", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + err = mw.Service.Logout(ctx) + return err +} + +func (mw metricsMiddleware) DestroySession(ctx context.Context) error { + var ( + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "DestroySession", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + err = mw.Service.DestroySession(ctx) + return err +} + +func (mw metricsMiddleware) GetInfoAboutSessionsForUser(ctx context.Context, id uint) ([]*kolide.Session, error) { + var ( + sessions []*kolide.Session + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "GetInfoAboutSessionsForUser", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + sessions, err = mw.Service.GetInfoAboutSessionsForUser(ctx, id) + return sessions, err +} + +func (mw metricsMiddleware) DeleteSessionsForUser(ctx context.Context, id uint) error { + var ( + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "DeleteSessionsForUser", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + err = mw.Service.DeleteSessionsForUser(ctx, id) + return err +} + +func (mw metricsMiddleware) GetInfoAboutSession(ctx context.Context, id uint) (*kolide.Session, error) { + var ( + session *kolide.Session + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "GetInfoAboutSession", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + session, err = mw.Service.GetInfoAboutSession(ctx, id) + return session, err +} + +func (mw metricsMiddleware) GetSessionByKey(ctx context.Context, key string) (*kolide.Session, error) { + var ( + session *kolide.Session + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "GetSessionByKey", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + session, err = mw.Service.GetSessionByKey(ctx, key) + return session, err +} + +func (mw metricsMiddleware) DeleteSession(ctx context.Context, id uint) error { + var ( + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "DeleteSession", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + err = mw.Service.DeleteSession(ctx, id) + return err +} diff --git a/server/service/metrics_users.go b/server/service/metrics_users.go new file mode 100644 index 0000000000..f28da7fe5d --- /dev/null +++ b/server/service/metrics_users.go @@ -0,0 +1,114 @@ +package service + +import ( + "fmt" + "time" + + "github.com/kolide/kolide-ose/server/kolide" + "golang.org/x/net/context" +) + +func (mw metricsMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) { + var ( + user *kolide.User + err error + ) + + defer func(begin time.Time) { + lvs := []string{"method", "NewUser", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + user, err = mw.Service.NewUser(ctx, p) + return user, err +} + +func (mw metricsMiddleware) ModifyUser(ctx context.Context, userID uint, p kolide.UserPayload) (*kolide.User, error) { + var ( + user *kolide.User + err error + ) + + defer func(begin time.Time) { + lvs := []string{"method", "ModifyUser", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + user, err = mw.Service.ModifyUser(ctx, userID, p) + return user, err +} + +func (mw metricsMiddleware) User(ctx context.Context, id uint) (*kolide.User, error) { + var ( + user *kolide.User + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "User", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + user, err = mw.Service.User(ctx, id) + return user, err +} + +func (mw metricsMiddleware) Users(ctx context.Context) ([]*kolide.User, error) { + + var ( + users []*kolide.User + err error + ) + defer func(begin time.Time) { + lvs := []string{"method", "Users", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + users, err = mw.Service.Users(ctx) + return users, err +} + +func (mw metricsMiddleware) AuthenticatedUser(ctx context.Context) (*kolide.User, error) { + var ( + user *kolide.User + err error + ) + + defer func(begin time.Time) { + lvs := []string{"method", "AuthenticatedUser", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + user, err = mw.Service.AuthenticatedUser(ctx) + return user, err +} + +func (mw metricsMiddleware) ResetPassword(ctx context.Context, token, password string) error { + var err error + + defer func(begin time.Time) { + lvs := []string{"method", "ResetPassword", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + err = mw.Service.ResetPassword(ctx, token, password) + return err +} + +func (mw metricsMiddleware) RequestPasswordReset(ctx context.Context, email string) error { + var err error + + defer func(begin time.Time) { + lvs := []string{"method", "RequestPasswordReset", "error", fmt.Sprint(err != nil)} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) + }(time.Now()) + + err = mw.Service.RequestPasswordReset(ctx, email) + return err +} diff --git a/server/service/service_appconfig.go b/server/service/service_appconfig.go index 6123c71e7b..3972171d8d 100644 --- a/server/service/service_appconfig.go +++ b/server/service/service_appconfig.go @@ -1,9 +1,8 @@ package service import ( - "context" - "github.com/kolide/kolide-ose/server/kolide" + "golang.org/x/net/context" ) func (svc service) NewOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) {