diff --git a/packages/cli-repl/src/cli-repl.ts b/packages/cli-repl/src/cli-repl.ts index 07d7e4394..59bce7dd6 100644 --- a/packages/cli-repl/src/cli-repl.ts +++ b/packages/cli-repl/src/cli-repl.ts @@ -43,6 +43,7 @@ import { promisify } from 'util'; import { getOsInfo } from './get-os-info'; import { UpdateNotificationManager } from './update-notification-manager'; import { markTime } from './startup-timing'; +import { SampledAnalytics } from '@mongosh/logging/lib/analytics-helpers'; /** * Connecting text key. @@ -500,13 +501,15 @@ export class CliRepl implements MongoshIOProvider { } as any /* axiosConfig and axiosRetryConfig are existing options, but don't have type definitions */ ); this.toggleableAnalytics = new ToggleableAnalytics( - new ThrottledAnalytics({ - target: this.segmentAnalytics, - throttle: { - rate: 30, - metadataPath: this.shellHomeDirectory.paths.shellLocalDataPath, - }, - }) + SampledAnalytics.default( + new ThrottledAnalytics({ + target: this.segmentAnalytics, + throttle: { + rate: 30, + metadataPath: this.shellHomeDirectory.paths.shellLocalDataPath, + }, + }) + ) ); } diff --git a/packages/logging/src/analytics-helpers.spec.ts b/packages/logging/src/analytics-helpers.spec.ts index 5ae787802..58811bef9 100644 --- a/packages/logging/src/analytics-helpers.spec.ts +++ b/packages/logging/src/analytics-helpers.spec.ts @@ -259,31 +259,23 @@ describe('analytics helpers', function () { properties: { mongosh_version: '1.2.3', session_id: 'abc' }, }; - it('should sample by default a 30% of open sessions with an error margin of ±1%', function () { - const expectedPercentage = 30; - // sampling is random based, so we must consider an error margin, for example, ±1% - const errorMargin = 1; - - let totalSample = 0; - let enabledSamples = 0; - - for (let i = 0; i < 100_000; i++) { - // evenly distributed randoms are more evenly with more samples - totalSample++; - enabledSamples += SampledAnalytics.default(target).enabled ? 1 : 0; - } + afterEach(function () { + delete process.env.MONGOSH_ANALYTICS_SAMPLE; + }); - const sampledPercentage = (enabledSamples / totalSample) * 100; - expect(sampledPercentage).to.be.greaterThan( - expectedPercentage - errorMargin - ); - expect(sampledPercentage).to.be.lessThan( - expectedPercentage + errorMargin - ); + it('should override sampling with the MONGOSH_ANALYTICS_SAMPLE environment variable', function () { + process.env.MONGOSH_ANALYTICS_SAMPLE = 'true'; + const analytics = SampledAnalytics.disabledForAll( + target + ) as SampledAnalytics; + + expect(analytics.enabled).to.be.true; }); it('should send the event forward when sampled', function () { - const analytics = SampledAnalytics.enabledForAll(target); + const analytics = SampledAnalytics.enabledForAll( + target + ) as SampledAnalytics; expect(analytics.enabled).to.be.true; analytics.identify(iEvt); @@ -293,7 +285,9 @@ describe('analytics helpers', function () { }); it('should not send the event forward when not sampled', function () { - const analytics = SampledAnalytics.disabledForAll(target); + const analytics = SampledAnalytics.disabledForAll( + target + ) as SampledAnalytics; expect(analytics.enabled).to.be.false; analytics.identify(iEvt); diff --git a/packages/logging/src/analytics-helpers.ts b/packages/logging/src/analytics-helpers.ts index 84ff58b4a..09a32ef9f 100644 --- a/packages/logging/src/analytics-helpers.ts +++ b/packages/logging/src/analytics-helpers.ts @@ -1,5 +1,6 @@ import fs from 'fs'; import path from 'path'; +import { performance } from 'perf_hooks'; export type MongoshAnalyticsIdentity = | { @@ -380,7 +381,7 @@ export class ThrottledAnalytics implements MongoshAnalytics { } type SampledAnalyticsOptions = { - target: MongoshAnalytics; + target?: MongoshAnalytics; /** * Sampling options. If not provided, sampling will be defaulted to 100% of sessions. * Also, from an exposed environment standpoint, providing a MONGOSH_ANALYTICS_SAMPLE @@ -391,40 +392,43 @@ type SampledAnalyticsOptions = { */ sampling: { percentage: number; - samplingFunction: () => number; + samplingFunction: (percentage: number) => boolean; } | null; }; export class SampledAnalytics implements MongoshAnalytics { private isEnabled: boolean; + private target: MongoshAnalytics; - private constructor(private configuration: SampledAnalyticsOptions) { - this.configuration.sampling ??= { + private constructor(configuration: SampledAnalyticsOptions) { + configuration.sampling ??= { percentage: 30, - samplingFunction: () => Math.random() * 100, + samplingFunction: (percentage) => Math.random() * 100 < percentage, }; - const shouldBeSampled = - this.configuration.sampling.samplingFunction() <= - this.configuration.sampling.percentage; + const shouldBeSampled = configuration.sampling.samplingFunction( + configuration.sampling.percentage + ); + this.isEnabled = !!process.env.MONGOSH_ANALYTICS_SAMPLE || shouldBeSampled; + this.target = configuration.target || new NoopAnalytics(); } - static default(target: MongoshAnalytics): SampledAnalytics { + static default(target?: MongoshAnalytics): MongoshAnalytics { return new SampledAnalytics({ target, sampling: null }); } - static enabledForAll(target: MongoshAnalytics): SampledAnalytics { + static enabledForAll(target?: MongoshAnalytics): MongoshAnalytics { return new SampledAnalytics({ target, - sampling: { percentage: 100, samplingFunction: () => 1 }, + sampling: { percentage: 100, samplingFunction: () => true }, }); } - static disabledForAll(target: MongoshAnalytics): SampledAnalytics { + static disabledForAll(target?: MongoshAnalytics): MongoshAnalytics { return new SampledAnalytics({ target, - sampling: { percentage: 0, samplingFunction: () => 1 }, + sampling: { percentage: 0, samplingFunction: () => false }, }); } @@ -433,14 +437,14 @@ export class SampledAnalytics implements MongoshAnalytics { } identify(message: AnalyticsIdentifyMessage): void { - this.isEnabled && this.configuration.target.identify(message); + this.isEnabled && this.target.identify(message); } track(message: AnalyticsTrackMessage): void { - this.isEnabled && this.configuration.target.track(message); + this.isEnabled && this.target.track(message); } flush(callback: (err?: Error | undefined) => void): void { - this.isEnabled && this.configuration.target.flush(callback); + this.isEnabled && this.target.flush(callback); } }