diff --git a/server/channels/app/job.go b/server/channels/app/job.go index a921d88a76c..d917b0f447d 100644 --- a/server/channels/app/job.go +++ b/server/channels/app/job.go @@ -175,6 +175,7 @@ func (a *App) SessionHasPermissionToReadJob(session model.Session, jobType strin model.JobTypeExportProcess, model.JobTypeExportDelete, model.JobTypeCloud, + model.JobTypeMobileSessionMetadata, model.JobTypeExtractContent: return a.SessionHasPermissionTo(session, model.PermissionReadJobs), model.PermissionReadJobs } diff --git a/server/channels/app/server.go b/server/channels/app/server.go index 5d6759ca2ee..03808e8a627 100644 --- a/server/channels/app/server.go +++ b/server/channels/app/server.go @@ -55,6 +55,7 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/jobs/last_accessible_file" "github.com/mattermost/mattermost/server/v8/channels/jobs/last_accessible_post" "github.com/mattermost/mattermost/server/v8/channels/jobs/migrations" + "github.com/mattermost/mattermost/server/v8/channels/jobs/mobile_session_metadata" "github.com/mattermost/mattermost/server/v8/channels/jobs/notify_admin" "github.com/mattermost/mattermost/server/v8/channels/jobs/plugins" "github.com/mattermost/mattermost/server/v8/channels/jobs/post_persistent_notifications" @@ -1565,6 +1566,12 @@ func (s *Server) initJobs() { active_users.MakeScheduler(s.Jobs), ) + s.Jobs.RegisterJobType( + model.JobTypeMobileSessionMetadata, + mobile_session_metadata.MakeWorker(s.Jobs, s.Store(), func() einterfaces.MetricsInterface { return s.GetMetrics() }), + mobile_session_metadata.MakeScheduler(s.Jobs), + ) + s.Jobs.RegisterJobType( model.JobTypeResendInvitationEmail, resend_invitation_email.MakeWorker(s.Jobs, New(ServerConnector(s.Channels())), s.Store(), s.telemetryService), diff --git a/server/channels/jobs/mobile_session_metadata/scheduler.go b/server/channels/jobs/mobile_session_metadata/scheduler.go new file mode 100644 index 00000000000..6a95284d879 --- /dev/null +++ b/server/channels/jobs/mobile_session_metadata/scheduler.go @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package mobile_session_metadata + +import ( + "time" + + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/v8/channels/jobs" +) + +const schedFreq = 24 * time.Hour + +func MakeScheduler(jobServer *jobs.JobServer) *jobs.PeriodicScheduler { + isEnabled := func(cfg *model.Config) bool { + return *cfg.MetricsSettings.EnableClientMetrics + } + return jobs.NewPeriodicScheduler(jobServer, model.JobTypeMobileSessionMetadata, schedFreq, isEnabled) +} diff --git a/server/channels/jobs/mobile_session_metadata/worker.go b/server/channels/jobs/mobile_session_metadata/worker.go new file mode 100644 index 00000000000..66c22662440 --- /dev/null +++ b/server/channels/jobs/mobile_session_metadata/worker.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package mobile_session_metadata + +import ( + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/mattermost/mattermost/server/v8/channels/jobs" + "github.com/mattermost/mattermost/server/v8/channels/store" + "github.com/mattermost/mattermost/server/v8/einterfaces" +) + +func MakeWorker(jobServer *jobs.JobServer, store store.Store, getMetrics func() einterfaces.MetricsInterface) *jobs.SimpleWorker { + const workerName = "MobileSessionMetadata" + + isEnabled := func(cfg *model.Config) bool { + return *cfg.MetricsSettings.EnableClientMetrics + } + execute := func(logger mlog.LoggerIFace, job *model.Job) error { + defer jobServer.HandleJobPanic(logger, job) + + metrics := getMetrics() + if metrics == nil { + return nil + } + + versions, err := store.Session().GetMobileSessionMetadata() + if err != nil { + return err + } + + metrics.ClearMobileClientSessionMetadata() + for _, v := range versions { + metrics.ObserveMobileClientSessionMetadata(v.Version, v.Platform, v.Count, v.NotificationDisabled) + } + + return nil + } + worker := jobs.NewSimpleWorker(workerName, jobServer, execute, isEnabled) + return worker +} diff --git a/server/channels/store/opentracinglayer/opentracinglayer.go b/server/channels/store/opentracinglayer/opentracinglayer.go index 26d196f1018..ec5ff4791ee 100644 --- a/server/channels/store/opentracinglayer/opentracinglayer.go +++ b/server/channels/store/opentracinglayer/opentracinglayer.go @@ -8710,6 +8710,24 @@ func (s *OpenTracingLayerSessionStore) GetLRUSessions(c request.CTX, userID stri return result, err } +func (s *OpenTracingLayerSessionStore) GetMobileSessionMetadata() ([]*model.MobileSessionMetadata, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.GetMobileSessionMetadata") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.SessionStore.GetMobileSessionMetadata() + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + func (s *OpenTracingLayerSessionStore) GetSessions(c request.CTX, userID string) ([]*model.Session, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.GetSessions") diff --git a/server/channels/store/retrylayer/retrylayer.go b/server/channels/store/retrylayer/retrylayer.go index 7419ac72c0d..e04b8c5a36e 100644 --- a/server/channels/store/retrylayer/retrylayer.go +++ b/server/channels/store/retrylayer/retrylayer.go @@ -9938,6 +9938,27 @@ func (s *RetryLayerSessionStore) GetLRUSessions(c request.CTX, userID string, li } +func (s *RetryLayerSessionStore) GetMobileSessionMetadata() ([]*model.MobileSessionMetadata, error) { + + tries := 0 + for { + result, err := s.SessionStore.GetMobileSessionMetadata() + if err == nil { + return result, nil + } + if !isRepeatableError(err) { + return result, err + } + tries++ + if tries >= 3 { + err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures") + return result, err + } + timepkg.Sleep(100 * timepkg.Millisecond) + } + +} + func (s *RetryLayerSessionStore) GetSessions(c request.CTX, userID string) ([]*model.Session, error) { tries := 0 diff --git a/server/channels/store/sqlstore/session_store.go b/server/channels/store/sqlstore/session_store.go index c0c0f308b54..7aa7c4bb04e 100644 --- a/server/channels/store/sqlstore/session_store.go +++ b/server/channels/store/sqlstore/session_store.go @@ -164,6 +164,38 @@ func (me SqlSessionStore) GetSessionsWithActiveDeviceIds(userId string) ([]*mode return sessions, nil } +func (me SqlSessionStore) GetMobileSessionMetadata() ([]*model.MobileSessionMetadata, error) { + versionProp := model.SessionPropMobileVersion + notificationDisabledProp := model.SessionPropDeviceNotificationDisabled + platformQuery := "NULLIF(SPLIT_PART(deviceid, ':', 1), '')" + if me.DriverName() == model.DatabaseDriverMysql { + versionProp = "$." + versionProp + notificationDisabledProp = "$." + notificationDisabledProp + platformQuery = "NULLIF(SUBSTRING_INDEX(deviceid, ':', 1), deviceid)" + } + + query, args, err := me.getQueryBuilder(). + Select(fmt.Sprintf( + "COUNT(userid) AS Count, COALESCE(%s,'N/A') AS Platform, COALESCE(props->>'%s','N/A') AS Version, COALESCE(props->>'%s','false') as NotificationDisabled", + platformQuery, + versionProp, + notificationDisabledProp, + )). + From("Sessions"). + GroupBy("Platform", "Version", "NotificationDisabled"). + ToSql() + if err != nil { + return nil, errors.Wrap(err, "sessions_tosql") + } + + versions := []*model.MobileSessionMetadata{} + err = me.GetReplicaX().Select(&versions, query, args...) + if err != nil { + return nil, errors.Wrap(err, "failed get mobile session metadata") + } + return versions, nil +} + func (me SqlSessionStore) GetSessionsExpired(thresholdMillis int64, mobileOnly bool, unnotifiedOnly bool) ([]*model.Session, error) { now := model.GetMillis() builder := me.getQueryBuilder(). diff --git a/server/channels/store/store.go b/server/channels/store/store.go index 2c33e7b3160..443363efeb3 100644 --- a/server/channels/store/store.go +++ b/server/channels/store/store.go @@ -503,6 +503,7 @@ type SessionStore interface { Save(c request.CTX, session *model.Session) (*model.Session, error) GetSessions(c request.CTX, userID string) ([]*model.Session, error) GetLRUSessions(c request.CTX, userID string, limit uint64, offset uint64) ([]*model.Session, error) + GetMobileSessionMetadata() ([]*model.MobileSessionMetadata, error) GetSessionsWithActiveDeviceIds(userID string) ([]*model.Session, error) GetSessionsExpired(thresholdMillis int64, mobileOnly bool, unnotifiedOnly bool) ([]*model.Session, error) UpdateExpiredNotify(sessionid string, notified bool) error diff --git a/server/channels/store/storetest/mocks/SessionStore.go b/server/channels/store/storetest/mocks/SessionStore.go index 590aef856db..d44ba3463a9 100644 --- a/server/channels/store/storetest/mocks/SessionStore.go +++ b/server/channels/store/storetest/mocks/SessionStore.go @@ -121,6 +121,36 @@ func (_m *SessionStore) GetLRUSessions(c request.CTX, userID string, limit uint6 return r0, r1 } +// GetMobileSessionMetadata provides a mock function with given fields: +func (_m *SessionStore) GetMobileSessionMetadata() ([]*model.MobileSessionMetadata, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetMobileSessionMetadata") + } + + var r0 []*model.MobileSessionMetadata + var r1 error + if rf, ok := ret.Get(0).(func() ([]*model.MobileSessionMetadata, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []*model.MobileSessionMetadata); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.MobileSessionMetadata) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetSessions provides a mock function with given fields: c, userID func (_m *SessionStore) GetSessions(c request.CTX, userID string) ([]*model.Session, error) { ret := _m.Called(c, userID) diff --git a/server/channels/store/storetest/session_store.go b/server/channels/store/storetest/session_store.go index 403bd1149c2..fc6e94f13d3 100644 --- a/server/channels/store/storetest/session_store.go +++ b/server/channels/store/storetest/session_store.go @@ -38,6 +38,7 @@ func TestSessionStore(t *testing.T, rctx request.CTX, ss store.Store) { t.Run("GetSessionsExpired", func(t *testing.T) { testGetSessionsExpired(t, rctx, ss) }) t.Run("UpdateExpiredNotify", func(t *testing.T) { testUpdateExpiredNotify(t, rctx, ss) }) t.Run("GetLRUSessions", func(t *testing.T) { testGetLRUSessions(t, rctx, ss) }) + t.Run("GetMobileSessionMetadata", func(t *testing.T) { testGetMobileSessionMetadata(t, rctx, ss) }) } func testSessionStoreSave(t *testing.T, rctx request.CTX, ss store.Store) { @@ -456,3 +457,84 @@ func testGetLRUSessions(t *testing.T, rctx request.CTX, ss store.Store) { require.Equal(t, s2.Id, sessions[1].Id) require.Equal(t, s1.Id, sessions[2].Id) } + +func testGetMobileSessionMetadata(t *testing.T, rctx request.CTX, ss store.Store) { + userId1 := model.NewId() + userId2 := model.NewId() + userId3 := model.NewId() + userId4 := model.NewId() + userId5 := model.NewId() + + // Clear existing sessions. + err := ss.Session().RemoveAllSessions() + require.NoError(t, err) + + s1 := &model.Session{} + s1.UserId = userId1 + s1.ExpiresAt = model.GetMillis() + 10000 + + _, err = ss.Session().Save(rctx, s1) + require.NoError(t, err) + + s2 := &model.Session{} + s2.UserId = userId2 + s2.DeviceId = "android:" + model.NewId() + s2.ExpiresAt = model.GetMillis() + 10000 + s2.Props = model.StringMap{ + model.SessionPropDeviceNotificationDisabled: "false", + model.SessionPropMobileVersion: "1.2.3", + } + + _, err = ss.Session().Save(rctx, s2) + require.NoError(t, err) + + s3 := &model.Session{} + s3.UserId = userId3 + s3.DeviceId = "ios:" + model.NewId() + s3.ExpiresAt = model.GetMillis() + 10000 + s3.Props = model.StringMap{ + model.SessionPropDeviceNotificationDisabled: "true", + model.SessionPropMobileVersion: "1.2.3", + } + + _, err = ss.Session().Save(rctx, s3) + require.NoError(t, err) + + s4 := &model.Session{} + s4.UserId = userId4 + s4.DeviceId = "android:" + model.NewId() + s4.ExpiresAt = model.GetMillis() + 10000 + s4.Props = model.StringMap{ + model.SessionPropDeviceNotificationDisabled: "true", + model.SessionPropMobileVersion: "3.2.1", + } + + _, err = ss.Session().Save(rctx, s4) + require.NoError(t, err) + + s5 := &model.Session{} + s5.UserId = userId5 + s5.DeviceId = "android:" + model.NewId() + s5.ExpiresAt = model.GetMillis() + 10000 + s5.Props = model.StringMap{ + model.SessionPropDeviceNotificationDisabled: "true", + model.SessionPropMobileVersion: "3.2.1", + } + + _, err = ss.Session().Save(rctx, s5) + require.NoError(t, err) + + metadata, err := ss.Session().GetMobileSessionMetadata() + require.NoError(t, err) + require.Len(t, metadata, 4) + found := false + for _, d := range metadata { + if d.NotificationDisabled == "true" && + d.Platform == "android" && + d.Version == "3.2.1" { + found = true + require.Equal(t, float64(2), d.Count) + } + } + require.True(t, found) +} diff --git a/server/channels/store/timerlayer/timerlayer.go b/server/channels/store/timerlayer/timerlayer.go index 48854c712a3..ef4bf769620 100644 --- a/server/channels/store/timerlayer/timerlayer.go +++ b/server/channels/store/timerlayer/timerlayer.go @@ -7849,6 +7849,22 @@ func (s *TimerLayerSessionStore) GetLRUSessions(c request.CTX, userID string, li return result, err } +func (s *TimerLayerSessionStore) GetMobileSessionMetadata() ([]*model.MobileSessionMetadata, error) { + start := time.Now() + + result, err := s.SessionStore.GetMobileSessionMetadata() + + elapsed := float64(time.Since(start)) / float64(time.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("SessionStore.GetMobileSessionMetadata", success, elapsed) + } + return result, err +} + func (s *TimerLayerSessionStore) GetSessions(c request.CTX, userID string) ([]*model.Session, error) { start := time.Now() diff --git a/server/einterfaces/metrics.go b/server/einterfaces/metrics.go index d9e5bff2687..54aca1fc58c 100644 --- a/server/einterfaces/metrics.go +++ b/server/einterfaces/metrics.go @@ -118,4 +118,6 @@ type MetricsInterface interface { ObserveMobileClientLoadDuration(platform string, elapsed float64) ObserveMobileClientChannelSwitchDuration(platform string, elapsed float64) ObserveMobileClientTeamSwitchDuration(platform string, elapsed float64) + ClearMobileClientSessionMetadata() + ObserveMobileClientSessionMetadata(version string, platform string, value float64, notificationDisabled string) } diff --git a/server/einterfaces/mocks/MetricsInterface.go b/server/einterfaces/mocks/MetricsInterface.go index e2163767dfd..76bb3ae1b2c 100644 --- a/server/einterfaces/mocks/MetricsInterface.go +++ b/server/einterfaces/mocks/MetricsInterface.go @@ -28,6 +28,11 @@ func (_m *MetricsInterface) AddMemCacheMissCounter(cacheName string, amount floa _m.Called(cacheName, amount) } +// ClearMobileClientSessionMetadata provides a mock function with given fields: +func (_m *MetricsInterface) ClearMobileClientSessionMetadata() { + _m.Called() +} + // DecrementHTTPWebSockets provides a mock function with given fields: originClient func (_m *MetricsInterface) DecrementHTTPWebSockets(originClient string) { _m.Called(originClient) @@ -373,6 +378,11 @@ func (_m *MetricsInterface) ObserveMobileClientLoadDuration(platform string, ela _m.Called(platform, elapsed) } +// ObserveMobileClientSessionMetadata provides a mock function with given fields: version, platform, value, notificationDisabled +func (_m *MetricsInterface) ObserveMobileClientSessionMetadata(version string, platform string, value float64, notificationDisabled string) { + _m.Called(version, platform, value, notificationDisabled) +} + // ObserveMobileClientTeamSwitchDuration provides a mock function with given fields: platform, elapsed func (_m *MetricsInterface) ObserveMobileClientTeamSwitchDuration(platform string, elapsed float64) { _m.Called(platform, elapsed) diff --git a/server/enterprise/metrics/metrics.go b/server/enterprise/metrics/metrics.go index 4fd9c862762..389778b182b 100644 --- a/server/enterprise/metrics/metrics.go +++ b/server/enterprise/metrics/metrics.go @@ -215,6 +215,7 @@ type MetricsInterfaceImpl struct { MobileClientLoadDuration *prometheus.HistogramVec MobileClientChannelSwitchDuration *prometheus.HistogramVec MobileClientTeamSwitchDuration *prometheus.HistogramVec + MobileClientSessionMetadataGauge *prometheus.GaugeVec } func init() { @@ -1335,6 +1336,17 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf ) m.Registry.MustRegister(m.MobileClientTeamSwitchDuration) + m.MobileClientSessionMetadataGauge = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystemClientsMobileApp, + Name: "mobile_session_metadata", + Help: "The number of mobile sessions in each version, platform and whether they have the notifications disabled", + }, + []string{"version", "platform", "notifications_disabled"}, + ) + m.Registry.MustRegister(m.MobileClientSessionMetadataGauge) + return m } @@ -1850,6 +1862,14 @@ func (mi *MetricsInterfaceImpl) ObserveMobileClientTeamSwitchDuration(platform s mi.MobileClientTeamSwitchDuration.With(prometheus.Labels{"platform": platform}).Observe(elapsed) } +func (mi *MetricsInterfaceImpl) ObserveMobileClientSessionMetadata(version string, platform string, value float64, notificationDisabled string) { + mi.MobileClientSessionMetadataGauge.With(prometheus.Labels{"version": version, "platform": platform, "notifications_disabled": notificationDisabled}).Set(value) +} + +func (mi *MetricsInterfaceImpl) ClearMobileClientSessionMetadata() { + mi.MobileClientSessionMetadataGauge.Reset() +} + func extractDBCluster(driver, connectionString string) (string, error) { host, err := extractHost(driver, connectionString) if err != nil { diff --git a/server/public/model/job.go b/server/public/model/job.go index 7db6860da50..500d4959acb 100644 --- a/server/public/model/job.go +++ b/server/public/model/job.go @@ -40,6 +40,7 @@ const ( JobTypeDeleteOrphanDraftsMigration = "delete_orphan_drafts_migration" JobTypeExportUsersToCSV = "export_users_to_csv" JobTypeDeleteDmsPreferencesMigration = "delete_dms_preferences_migration" + JobTypeMobileSessionMetadata = "mobile_session_metadata" JobStatusPending = "pending" JobStatusInProgress = "in_progress" @@ -72,6 +73,7 @@ var AllJobTypes = [...]string{ JobTypeLastAccessibleFile, JobTypeCleanupDesktopTokens, JobTypeRefreshPostStats, + JobTypeMobileSessionMetadata, } type Job struct { diff --git a/server/public/model/session.go b/server/public/model/session.go index 55e54d1c39d..4cfe253d924 100644 --- a/server/public/model/session.go +++ b/server/public/model/session.go @@ -39,6 +39,13 @@ const ( //msgp:tuple StringMap type StringMap map[string]string +type MobileSessionMetadata struct { + Version string + Platform string + Count float64 + NotificationDisabled string +} + // Session contains the user session details. // This struct's serializer methods are auto-generated. If a new field is added/removed, // please run make gen-serialized. diff --git a/server/public/model/session_serial_gen.go b/server/public/model/session_serial_gen.go index 612bbb8977f..f962cc03cd6 100644 --- a/server/public/model/session_serial_gen.go +++ b/server/public/model/session_serial_gen.go @@ -9,6 +9,184 @@ import ( "github.com/tinylib/msgp/msgp" ) +// DecodeMsg implements msgp.Decodable +func (z *MobileSessionMetadata) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "Version": + z.Version, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + case "Platform": + z.Platform, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Platform") + return + } + case "Count": + z.Count, err = dc.ReadFloat64() + if err != nil { + err = msgp.WrapError(err, "Count") + return + } + case "NotificationDisabled": + z.NotificationDisabled, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "NotificationDisabled") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *MobileSessionMetadata) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 4 + // write "Version" + err = en.Append(0x84, 0xa7, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e) + if err != nil { + return + } + err = en.WriteString(z.Version) + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + // write "Platform" + err = en.Append(0xa8, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d) + if err != nil { + return + } + err = en.WriteString(z.Platform) + if err != nil { + err = msgp.WrapError(err, "Platform") + return + } + // write "Count" + err = en.Append(0xa5, 0x43, 0x6f, 0x75, 0x6e, 0x74) + if err != nil { + return + } + err = en.WriteFloat64(z.Count) + if err != nil { + err = msgp.WrapError(err, "Count") + return + } + // write "NotificationDisabled" + err = en.Append(0xb4, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64) + if err != nil { + return + } + err = en.WriteString(z.NotificationDisabled) + if err != nil { + err = msgp.WrapError(err, "NotificationDisabled") + return + } + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *MobileSessionMetadata) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 4 + // string "Version" + o = append(o, 0x84, 0xa7, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e) + o = msgp.AppendString(o, z.Version) + // string "Platform" + o = append(o, 0xa8, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d) + o = msgp.AppendString(o, z.Platform) + // string "Count" + o = append(o, 0xa5, 0x43, 0x6f, 0x75, 0x6e, 0x74) + o = msgp.AppendFloat64(o, z.Count) + // string "NotificationDisabled" + o = append(o, 0xb4, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64) + o = msgp.AppendString(o, z.NotificationDisabled) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *MobileSessionMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "Version": + z.Version, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + case "Platform": + z.Platform, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Platform") + return + } + case "Count": + z.Count, bts, err = msgp.ReadFloat64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Count") + return + } + case "NotificationDisabled": + z.NotificationDisabled, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "NotificationDisabled") + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + o = bts + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *MobileSessionMetadata) Msgsize() (s int) { + s = 1 + 8 + msgp.StringPrefixSize + len(z.Version) + 9 + msgp.StringPrefixSize + len(z.Platform) + 6 + msgp.Float64Size + 21 + msgp.StringPrefixSize + len(z.NotificationDisabled) + return +} + // DecodeMsg implements msgp.Decodable func (z *Session) DecodeMsg(dc *msgp.Reader) (err error) { var zb0001 uint32