Skip to content

Remove the capability of sending events to clearcut from the Performance SDK. #3086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 24 additions & 120 deletions packages/performance/src/services/remote_config_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { SettingsService } from './settings_service';
import { CONFIG_EXPIRY_LOCAL_STORAGE_KEY } from '../constants';
import { setupApi, Api } from './api_service';
import * as iidService from './iid_service';
import { getConfig, isDestFl } from './remote_config_service';
import { getConfig } from './remote_config_service';
import { FirebaseApp } from '@firebase/app-types';
import '../../test/setup';

Expand All @@ -37,7 +37,6 @@ describe('Performance Monitoring > remote_config_service', () => {
"fpr_log_endpoint_url":"https://firebaselogging.test.com",\
"fpr_log_transport_key":"pseudo-transport-key",\
"fpr_log_source":"2","fpr_vc_network_request_sampling_rate":"0.250000",\
"fpr_log_transport_web_percent":"100.0",\
"fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\
"state":"UPDATE"}`;
const PROJECT_ID = 'project1';
Expand Down Expand Up @@ -135,7 +134,6 @@ describe('Performance Monitoring > remote_config_service', () => {
TRANSPORT_KEY
);
expect(SettingsService.getInstance().logSource).to.equal(LOG_SOURCE);
expect(SettingsService.getInstance().shouldSendToFl).to.be.true;
expect(
SettingsService.getInstance().networkRequestsSamplingRate
).to.equal(NETWORK_SAMPLIG_RATE);
Expand Down Expand Up @@ -176,7 +174,6 @@ describe('Performance Monitoring > remote_config_service', () => {
TRANSPORT_KEY
);
expect(SettingsService.getInstance().logSource).to.equal(LOG_SOURCE);
expect(SettingsService.getInstance().shouldSendToFl).to.be.true;
expect(
SettingsService.getInstance().networkRequestsSamplingRate
).to.equal(NETWORK_SAMPLIG_RATE);
Expand Down Expand Up @@ -240,131 +237,38 @@ describe('Performance Monitoring > remote_config_service', () => {
expect(SettingsService.getInstance().loggingEnabled).to.be.true;
});

it('marks event destination to cc if there is no template', async () => {
setup(
{
// Expired local config.
expiry: '1556524895320',
config: NOT_VALID_CONFIG
},
{ reject: false, value: new Response('{"state":"NO_TEMPLATE"}') }
);
await getConfig(IID);

// If no template, will send to cc.
expect(SettingsService.getInstance().shouldSendToFl).to.be.false;
});

it('marks event destination to cc if instance state unspecified', async () => {
setup(
{
// Expired local config.
expiry: '1556524895320',
config: NOT_VALID_CONFIG
},
{
reject: false,
value: new Response('{"state":"INSTANCE_STATE_UNSPECIFIED"}')
}
);
await getConfig(IID);

// If instance state unspecified, will send to cc.
expect(SettingsService.getInstance().shouldSendToFl).to.be.false;
});
it('gets the config from RC even with deprecated transport flag', async () => {
// Expired local config.
const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895320';
const STRINGIFIED_CUSTOM_CONFIG = `{"entries":{\
"fpr_vc_network_request_sampling_rate":"0.250000",\
"fpr_log_transport_web_percent":"100.0",\
"fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\
"state":"UPDATE"}`;

it("marks event destination to cc if state doesn't exist", async () => {
setup(
const { storageGetItemStub: getItemStub } = setup(
{
// Expired local config.
expiry: '1556524895320',
config: NOT_VALID_CONFIG
expiry: EXPIRY_LOCAL_STORAGE_VALUE,
config: STRINGIFIED_CUSTOM_CONFIG
},
{ reject: false, value: new Response('{}') }
{ reject: false, value: new Response(STRINGIFIED_CONFIG) }
);
await getConfig(IID);

// If "state" doesn't exist, will send to cc.
expect(SettingsService.getInstance().shouldSendToFl).to.be.false;
});

it('marks event destination to Fl if template exists but no rollout flag', async () => {
const CONFIG_WITHOUT_ROLLOUT_FLAG = `{"entries":{"fpr_enabled":"true",\
"fpr_log_endpoint_url":"https://firebaselogging.test.com",\
"fpr_log_source":"2","fpr_vc_network_request_sampling_rate":"0.250000",\
"fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\
"state":"UPDATE"}`;
setup(
{
// Expired local config.
expiry: '1556524895320',
config: NOT_VALID_CONFIG
},
{ reject: false, value: new Response(CONFIG_WITHOUT_ROLLOUT_FLAG) }
);
await getConfig(IID);

// If template exists but no rollout flag, will send to Fl.
expect(SettingsService.getInstance().shouldSendToFl).to.be.true;
});

it('marks event destination to cc when instance is outside of rollout range', async () => {
const CONFIG_WITH_ROLLOUT_FLAG_10 = `{"entries":{"fpr_enabled":"true",\
"fpr_log_endpoint_url":"https://firebaselogging.test.com",\
"fpr_log_source":"2","fpr_vc_network_request_sampling_rate":"0.250000",\
"fpr_log_transport_web_percent":"10.0",\
"fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\
"state":"UPDATE"}`;
setup(
{
// Expired local config.
expiry: '1556524895320',
config: NOT_VALID_CONFIG
},
{ reject: false, value: new Response(CONFIG_WITH_ROLLOUT_FLAG_10) }
expect(getItemStub).to.be.calledOnce;
expect(SettingsService.getInstance().loggingEnabled).to.be.true;
expect(SettingsService.getInstance().logEndPointUrl).to.equal(LOG_URL);
expect(SettingsService.getInstance().transportKey).to.equal(
TRANSPORT_KEY
);
await getConfig(IID);

// If rollout flag exists, will send to cc when this instance is out of rollout scope.
expect(SettingsService.getInstance().shouldSendToFl).to.be.false;
});

it('marks event destination to Fl when instance is within rollout range', async () => {
const CONFIG_WITH_ROLLOUT_FLAG_40 = `{"entries":{"fpr_enabled":"true",\
"fpr_log_endpoint_url":"https://firebaselogging.test.com",\
"fpr_log_source":"2","fpr_vc_network_request_sampling_rate":"0.250000",\
"fpr_log_transport_web_percent":"40.0",\
"fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\
"state":"UPDATE"}`;
setup(
{
// Expired local config.
expiry: '1556524895320',
config: NOT_VALID_CONFIG
},
{ reject: false, value: new Response(CONFIG_WITH_ROLLOUT_FLAG_40) }
expect(SettingsService.getInstance().logSource).to.equal(LOG_SOURCE);
expect(
SettingsService.getInstance().networkRequestsSamplingRate
).to.equal(NETWORK_SAMPLIG_RATE);
expect(SettingsService.getInstance().tracesSamplingRate).to.equal(
TRACE_SAMPLING_RATE
);
await getConfig(IID);

// If rollout flag exists, will send to Fl when this instance is within rollout scope.
expect(SettingsService.getInstance().shouldSendToFl).to.be.true;
});
});

describe('isDestFl', () => {
it('marks traffic to cc when rollout percentage is 0', () => {
const shouldSendToFl = isDestFl('abc', 0); // Hash percentage of "abc" is 38%.
expect(shouldSendToFl).to.be.false;
});

it('marks traffic to Fl when rollout percentage is 100', () => {
const shouldSendToFl = isDestFl('abc', 100); // Hash percentage of "abc" is 38%.
expect(shouldSendToFl).to.be.true;
});

it('marks traffic to Fl if hash percentage is lower than rollout percentage 50%', () => {
const shouldSendToFl = isDestFl('abc', 50); // Hash percentage of "abc" is 38%.
expect(shouldSendToFl).to.be.true;
});
});
});
71 changes: 4 additions & 67 deletions packages/performance/src/services/remote_config_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,14 @@ interface SecondaryConfig {
logSource?: number;
logEndPointUrl?: string;
transportKey?: string;
shouldSendToFl?: boolean;
tracesSamplingRate?: number;
networkRequestsSamplingRate?: number;
}

// These values will be used if the remote config object is successfully
// retrieved, but the template does not have these fields.
const DEFAULT_CONFIGS: SecondaryConfig = {
loggingEnabled: true,
shouldSendToFl: true
};

// These values will be used if the remote config object is successfully
// retrieved, but the config object state shows unspecified or no template.
const NO_TEMPLATE_CONFIGS: SecondaryConfig = {
shouldSendToFl: false
loggingEnabled: true
};

/* eslint-disable camelcase */
Expand All @@ -75,12 +67,12 @@ const FIS_AUTH_PREFIX = 'FIREBASE_INSTALLATIONS_AUTH';
export function getConfig(iid: string): Promise<void> {
const config = getStoredConfig();
if (config) {
processConfig(iid, config);
processConfig(config);
return Promise.resolve();
}

return getRemoteConfig(iid)
.then(config => processConfig(iid, config))
.then(processConfig)
.then(
config => storeConfig(config),
/** Do nothing for error, use defaults set in settings service. */
Expand Down Expand Up @@ -170,15 +162,13 @@ function getRemoteConfig(
* is valid.
*/
function processConfig(
iid: string,
config: RemoteConfigResponse | undefined
config?: RemoteConfigResponse
): RemoteConfigResponse | undefined {
if (!config) {
return config;
}
const settingsServiceInstance = SettingsService.getInstance();
const entries = config.entries || {};
const state = config.state;
if (entries.fpr_enabled !== undefined) {
// TODO: Change the assignment of loggingEnabled once the received type is
// known.
Expand Down Expand Up @@ -208,32 +198,6 @@ function processConfig(
settingsServiceInstance.transportKey = DEFAULT_CONFIGS.transportKey;
}

// If config object state indicates that no template has been set, that means it is new user of
// Performance Monitoring and should use the old log endpoint, because it is guaranteed to work.
if (
state === undefined ||
state === 'INSTANCE_STATE_UNSPECIFIED' ||
state === 'NO_TEMPLATE'
) {
if (NO_TEMPLATE_CONFIGS.shouldSendToFl !== undefined) {
settingsServiceInstance.shouldSendToFl =
NO_TEMPLATE_CONFIGS.shouldSendToFl;
}
} else if (entries.fpr_log_transport_web_percent) {
// If config object state doesn't indicate no template, it can only be UPDATE for now.
// - Performance Monitoring doesn't set etag in request, therefore state cannot be NO_CHANGE.
// - Sampling rate flags and master flag are required, therefore state cannot be EMPTY_CONFIG.
// If config object state is UPDATE and rollout flag is present, determine endpoint by iid.
settingsServiceInstance.shouldSendToFl = isDestFl(
iid,
Number(entries.fpr_log_transport_web_percent)
);
} else if (DEFAULT_CONFIGS.shouldSendToFl !== undefined) {
// If config object state is UPDATE and rollout flag is not present, that means rollout is
// complete and rollout flag is deprecated, therefore dispatch events to new transport endpoint.
settingsServiceInstance.shouldSendToFl = DEFAULT_CONFIGS.shouldSendToFl;
}

if (entries.fpr_vc_network_request_sampling_rate !== undefined) {
settingsServiceInstance.networkRequestsSamplingRate = Number(
entries.fpr_vc_network_request_sampling_rate
Expand Down Expand Up @@ -267,30 +231,3 @@ function configValid(expiry: string): boolean {
function shouldLogAfterSampling(samplingRate: number): boolean {
return Math.random() <= samplingRate;
}

/**
* True if event should be sent to Fl transport endpoint rather than CC transport endpoint.
* rolloutPercent is in range [0.0, 100.0].
* @param iid Installation ID which identifies a web app installed on client.
* @param rolloutPercent the possibility of this app sending events to Fl endpoint.
* @return true if this installation should send events to Fl endpoint.
*/
export function isDestFl(iid: string, rolloutPercent: number): boolean {
if (iid.length === 0) {
return false;
}
return getHashPercent(iid) < rolloutPercent;
}
/**
* Generate integer value range in [0, 99]. Implementation from String.hashCode() in Java.
* @param seed Same seed will generate consistent hash value using this algorithm.
* @return Hash value in range [0, 99], generated from seed and hash algorithm.
*/
function getHashPercent(seed: string): number {
let hash = 0;
for (let i = 0; i < seed.length; i++) {
hash = (hash << 3) + hash - seed.charCodeAt(i);
}
hash = Math.abs(hash % 100);
return hash;
}
2 changes: 0 additions & 2 deletions packages/performance/src/services/settings_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export class SettingsService {

transportKey = mergeStrings('AzSC8r6ReiGqFMyfvgow', 'Iayx0u-XT3vksVM-pIV');

shouldSendToFl = false;

// Source type for performance event logs.
logSource = 462;

Expand Down
2 changes: 0 additions & 2 deletions packages/performance/src/services/transport_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ describe('Firebase Performance > transport_service', () => {
fetchStub.restore();
clock.restore();
resetTransportService();
SettingsService.getInstance().shouldSendToFl = false;
});

it('throws an error when logging an empty message', () => {
Expand Down Expand Up @@ -87,7 +86,6 @@ describe('Firebase Performance > transport_service', () => {
const setting = SettingsService.getInstance();
const flTransportFullUrl =
setting.flTransportEndpointUrl + '?key=' + setting.transportKey;
setting.shouldSendToFl = true;
fetchStub.withArgs(flTransportFullUrl, match.any).resolves(
// DELETE_REQUEST means event dispatch is successful.
new Response(
Expand Down
37 changes: 1 addition & 36 deletions packages/performance/src/services/transport_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function dispatchQueueEvents(): void {
};
/* eslint-enable camelcase */

postToEndpoint(data, staged).catch(() => {
sendEventsToFl(data, staged).catch(() => {
// If the request fails for some reason, add the events that were attempted
// back to the primary queue to retry later.
queue = [...staged, ...queue];
Expand All @@ -122,18 +122,6 @@ function dispatchQueueEvents(): void {
});
}

function postToEndpoint(
data: TransportBatchLogFormat,
staged: BatchEvent[]
): Promise<void> {
// Gradually rollout traffic from cc to transport using remote config.
if (SettingsService.getInstance().shouldSendToFl) {
return sendEventsToFl(data, staged);
} else {
return sendEventsToCc(data);
}
}

function sendEventsToFl(
data: TransportBatchLogFormat,
staged: BatchEvent[]
Expand Down Expand Up @@ -171,29 +159,6 @@ function sendEventsToFl(
});
}

function sendEventsToCc(data: TransportBatchLogFormat): Promise<void> {
return fetch(SettingsService.getInstance().logEndPointUrl, {
method: 'POST',
body: JSON.stringify(data)
})
.then(res => {
if (!res.ok) {
consoleLogger.info('Call to Firebase backend failed.');
}
return res.json();
})
.then(res => {
const wait = Number(res.next_request_wait_millis);
// Find the next call wait time from the response.
const requestOffset = isNaN(wait)
? DEFAULT_SEND_INTERVAL_MS
: Math.max(DEFAULT_SEND_INTERVAL_MS, wait);
remainingTries = DEFAULT_REMAINING_TRIES;
// Schedule the next process.
processQueue(requestOffset);
});
}

function postToFlEndpoint(data: TransportBatchLogFormat): Promise<Response> {
const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl();
return fetch(flTransportFullUrl, {
Expand Down