Skip to content

Crashlytics ndk datatransport #1356

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 27 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
54ab654
Add FilesPayload for NDK reports
mrwillis21 Mar 5, 2020
3a1d179
WIP
mrwillis21 Mar 10, 2020
9d5bc02
Implement strategy pattern for native sessions
mrwillis21 Mar 11, 2020
aae1642
Prep for next iteration
mrwillis21 Mar 12, 2020
84c793c
Refactor datatransport abstractions to support polymorphic handling o…
jakeouellette Mar 13, 2020
70be19a
Address feedback
jakeouellette Mar 17, 2020
867e464
style
jakeouellette Mar 17, 2020
89e196e
remove UTF-8
jakeouellette Mar 17, 2020
8371820
Add test classes
jakeouellette Mar 17, 2020
7a76a10
Add tests
jakeouellette Mar 17, 2020
745cd56
Add tests and serialization of ndkpayload
jakeouellette Mar 17, 2020
1d16106
Add settings field for NDK sessions through DataTransport
mrwillis21 Mar 17, 2020
d533e8b
Wire up settings to send procedure
jakeouellette Mar 17, 2020
a924bb2
Interleave native and non-native high priority sessions
jakeouellette Mar 17, 2020
fcc5426
Add session ID abstraction to facilitate deleting NDK reports
jakeouellette Mar 17, 2020
1796c16
Add tests for session coordinator and report persistence
jakeouellette Mar 18, 2020
bcacb59
Make Firebase session ID for native sessions
mrwillis21 Mar 18, 2020
56a4eeb
Add support for sending/deleting native crashes based on the settings…
mrwillis21 Mar 18, 2020
75257bf
Cleanup
mrwillis21 Mar 18, 2020
21313d7
Remove undone testS
jakeouellette Mar 18, 2020
9173669
Cleanup
mrwillis21 Mar 18, 2020
3d231e2
Base ndk sessions on non-ndk sessions
jakeouellette Mar 18, 2020
365d773
Update paths to allow firelog to go to a different filename.
jakeouellette Mar 19, 2020
cba4bff
Make try { } catches less wide
jakeouellette Mar 19, 2020
8d54a9c
Remove old withUserId method
mrwillis21 Mar 19, 2020
f2f3db3
Add null check for withSessionEndFields
mrwillis21 Mar 19, 2020
5c3f488
Replace builder with simpler create method
mrwillis21 Mar 19, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ protected void setUp() throws Exception {
final SettingsData testSettingsData =
new TestSettingsData(
3,
CrashlyticsController.REPORT_UPLOAD_VARIANT_LEGACY,
CrashlyticsController.REPORT_UPLOAD_VARIANT_LEGACY);
DataTransportState.REPORT_UPLOAD_VARIANT_LEGACY,
DataTransportState.REPORT_UPLOAD_VARIANT_LEGACY);
appSettingsData = testSettingsData.appData;
sessionSettingsData = testSettingsData.sessionData;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,8 @@ private Task<CrashlyticsCore> startCoreAsync(CrashlyticsCore crashlyticsCore) {
final SettingsData settings =
new TestSettingsData(
3,
CrashlyticsController.REPORT_UPLOAD_VARIANT_LEGACY,
CrashlyticsController.REPORT_UPLOAD_VARIANT_LEGACY);
DataTransportState.REPORT_UPLOAD_VARIANT_LEGACY,
DataTransportState.REPORT_UPLOAD_VARIANT_LEGACY);
when(mockSettingsController.getSettings()).thenReturn(settings);
when(mockSettingsController.getAppSettings()).thenReturn(Tasks.forResult(settings.appData));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.crashlytics.internal.common.SessionReportingCoordinator.SendReportPredicate;
import com.google.firebase.crashlytics.internal.log.LogFileManager;
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.CustomAttribute;
Expand All @@ -55,8 +54,6 @@ public class SessionReportingCoordinatorTest {
@Mock private DataTransportCrashlyticsReportSender reportSender;
@Mock private LogFileManager logFileManager;
@Mock private UserMetadata reportMetadata;
@Mock private SendReportPredicate mockSendReportPredicate;
@Mock private SendReportPredicate mockSendNativeReportPredicate;
@Mock private CrashlyticsReport mockReport;
@Mock private CrashlyticsReport.Session.Event mockEvent;
@Mock private CrashlyticsReport.Session.Event.Builder mockEventBuilder;
Expand Down Expand Up @@ -360,8 +357,6 @@ public void onSessionsFinalize_finalizesReports() {
@Test
@SuppressWarnings("unchecked")
public void onReportSend_successfulReportsAreDeleted() {
when(mockSendReportPredicate.shouldSendViaDataTransport()).thenReturn(true);
when(mockSendNativeReportPredicate.shouldSendViaDataTransport()).thenReturn(true);
final String orgId = "testOrgId";
final String sessionId1 = "sessionId1";
final String sessionId2 = "sessionId2";
Expand All @@ -381,8 +376,7 @@ public void onReportSend_successfulReportsAreDeleted() {
when(reportSender.sendReport(mockReport1)).thenReturn(successfulTask);
when(reportSender.sendReport(mockReport2)).thenReturn(failedTask);

reportManager.sendReports(
orgId, Runnable::run, mockSendReportPredicate, mockSendNativeReportPredicate);
reportManager.sendReports(orgId, Runnable::run, DataTransportState.ALL);

verify(reportSender).sendReport(mockReport1);
verify(reportSender).sendReport(mockReport2);
Expand All @@ -392,19 +386,43 @@ public void onReportSend_successfulReportsAreDeleted() {
}

@Test
public void onReportSend_reportsAreDeletedWithoutBeingSent_whenSendPredicateIsFalse() {
when(mockSendReportPredicate.shouldSendViaDataTransport()).thenReturn(false);
when(mockSendNativeReportPredicate.shouldSendViaDataTransport()).thenReturn(false);
reportManager.sendReports(
"testOrgId", Runnable::run, mockSendReportPredicate, mockSendNativeReportPredicate);
public void onReportSend_reportsAreDeletedWithoutBeingSent_whenDataTransportStateNone() {
reportManager.sendReports("testOrgId", Runnable::run, DataTransportState.NONE);

verify(reportPersistence).deleteAllReports();
verify(reportPersistence, never()).loadFinalizedReports();
verify(reportPersistence, never()).deleteFinalizedReport(anyString());
verifyZeroInteractions(reportSender);
}

// FIXME: Add a test here
@Test
public void
onReportSend_javaReportsAreSentNativeReportsDeletedWithoutBeingSent_whenDataTransportStateJavaOnly() {
final String orgId = "testOrgId";
final String sessionId1 = "sessionId1";
final String sessionId2 = "sessionId2";

final List<CrashlyticsReportWithSessionId> finalizedReports = new ArrayList<>();
final CrashlyticsReportWithSessionId mockReport1 = mockReportWithSessionId(sessionId1, orgId);
final CrashlyticsReportWithSessionId mockReport2 = mockReportWithSessionId(sessionId2, orgId);
finalizedReports.add(mockReport1);
finalizedReports.add(mockReport2);

when(mockReport1.getReport().getType()).thenReturn(CrashlyticsReport.Type.JAVA);
when(mockReport2.getReport().getType()).thenReturn(CrashlyticsReport.Type.NATIVE);

when(reportPersistence.loadFinalizedReports()).thenReturn(finalizedReports);

when(reportSender.sendReport(mockReport1)).thenReturn(Tasks.forResult(mockReport1));

reportManager.sendReports(orgId, Runnable::run, DataTransportState.JAVA_ONLY);

verify(reportSender).sendReport(mockReport1);
verify(reportSender, never()).sendReport(mockReport2);

verify(reportPersistence).deleteFinalizedReport(sessionId1);
verify(reportPersistence).deleteFinalizedReport(sessionId2);
}

@Test
public void testPersistUserIdForCurrentSession_persistsCurrentUserIdForCurrentSessionId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.mockito.Mockito.when;

import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
import com.google.firebase.crashlytics.internal.common.DataTransportState;
import com.google.firebase.crashlytics.internal.common.TestNativeReportFilesProvider;
import com.google.firebase.crashlytics.internal.common.TestReportFilesProvider;
import com.google.firebase.crashlytics.internal.report.model.CreateReportRequest;
Expand Down Expand Up @@ -57,7 +58,7 @@ protected void setUp() throws Exception {
new ReportUploader(
"testOrganizationId",
"testGoogleAppId",
true,
DataTransportState.NONE,
reportManager,
mockCall,
mockHandlingExceptionCheck);
Expand Down Expand Up @@ -113,13 +114,13 @@ public void testRetry() throws Exception {
verifyZeroInteractions(mockCall);
}

public void testSendReport_deletesWithoutSendingWhenNotUsingReportsEndpoint() throws Exception {
final boolean isUsingReportsEndpoint = false;
public void testSendReport_deletesWithoutSendingWhenDataTransportAll() throws Exception {
final DataTransportState dataTransportState = DataTransportState.ALL;
reportUploader =
new ReportUploader(
"testOrganizationId",
"testGoogleAppId",
isUsingReportsEndpoint,
dataTransportState,
reportManager,
mockCall,
mockHandlingExceptionCheck);
Expand All @@ -133,17 +134,17 @@ public void testSendReport_deletesWithoutSendingWhenNotUsingReportsEndpoint() th
verifyZeroInteractions(mockCall);
}

public void testSendReport_sendsNativeCrashesRegardlessOfEndpointFlag() throws Exception {
public void testSendReport_sendsNativeCrashesWhenDataTransportJavaOnly() throws Exception {
final TestNativeReportFilesProvider nativeReportFilesProvider =
new TestNativeReportFilesProvider(getContext());
reportManager = new ReportManager(nativeReportFilesProvider);

final boolean isUsingReportsEndpoint = false;
final DataTransportState dataTransportState = DataTransportState.JAVA_ONLY;
reportUploader =
new ReportUploader(
"testOrganizationId",
"testGoogleAppId",
isUsingReportsEndpoint,
dataTransportState,
reportManager,
mockCall,
mockHandlingExceptionCheck);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public void setUp() throws Exception {
public void testSendReportsSuccessful() throws Exception {
doAnswer(callbackAnswer(null)).when(mockTransport).schedule(any(), any());

final CrashlyticsReportWithSessionId report1 = mock(CrashlyticsReportWithSessionId.class);
final CrashlyticsReportWithSessionId report2 = mock(CrashlyticsReportWithSessionId.class);
final CrashlyticsReportWithSessionId report1 = mockReportWithSessionId();
final CrashlyticsReportWithSessionId report2 = mockReportWithSessionId();

final Task<CrashlyticsReportWithSessionId> send1 = reportSender.sendReport(report1);
final Task<CrashlyticsReportWithSessionId> send2 = reportSender.sendReport(report2);
Expand All @@ -77,8 +77,8 @@ public void testSendReportsFailure() throws Exception {
final Exception ex = new Exception("fail");
doAnswer(callbackAnswer(ex)).when(mockTransport).schedule(any(), any());

final CrashlyticsReportWithSessionId report1 = mock(CrashlyticsReportWithSessionId.class);
final CrashlyticsReportWithSessionId report2 = mock(CrashlyticsReportWithSessionId.class);
final CrashlyticsReportWithSessionId report1 = mockReportWithSessionId();
final CrashlyticsReportWithSessionId report2 = mockReportWithSessionId();

final Task<CrashlyticsReportWithSessionId> send1 = reportSender.sendReport(report1);
final Task<CrashlyticsReportWithSessionId> send2 = reportSender.sendReport(report2);
Expand All @@ -104,8 +104,8 @@ public void testSendReports_oneSuccessOneFail() throws Exception {
.when(mockTransport)
.schedule(any(), any());

final CrashlyticsReportWithSessionId report1 = mock(CrashlyticsReportWithSessionId.class);
final CrashlyticsReportWithSessionId report2 = mock(CrashlyticsReportWithSessionId.class);
final CrashlyticsReportWithSessionId report1 = mockReportWithSessionId();
final CrashlyticsReportWithSessionId report2 = mockReportWithSessionId();

final Task<CrashlyticsReportWithSessionId> send1 = reportSender.sendReport(report1);
final Task<CrashlyticsReportWithSessionId> send2 = reportSender.sendReport(report2);
Expand All @@ -130,4 +130,11 @@ private static Answer<Void> callbackAnswer(Exception failure) {
return null;
};
}

private static CrashlyticsReportWithSessionId mockReportWithSessionId() {
return CrashlyticsReportWithSessionId.builder()
.setSessionId("sessionId")
.setReport(mock(CrashlyticsReport.class))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,6 @@ class CrashlyticsController {
static final String FIREBASE_APPLICATION_EXCEPTION = "_ae";
static final String FIREBASE_ANALYTICS_ORIGIN_CRASHLYTICS = "clx";

// Used to determine whether to upload reports through the legacy reports endpoint
static final int REPORT_UPLOAD_VARIANT_LEGACY = 1;
// Used to determine whether to upload reports through the new DataTransport API.
static final int REPORT_UPLOAD_VARIANT_DATATRANSPORT = 2;

// region CLS File filters for retrieving specific sets of files.

/** File filter that matches if a specified string is contained in the file name. */
Expand Down Expand Up @@ -433,9 +428,7 @@ public Task<Void> then(@Nullable AppSettingsData appSettingsData)
reportingCoordinator.sendReports(
appSettingsData.organizationId,
executor,
shouldSendViaDataTransport(appSettingsData.reportUploadVariant),
shouldSendViaDataTransport(
appSettingsData.nativeReportUploadVariant));
DataTransportState.getState(appSettingsData));
return recordFatalFirebaseEventTask;
}
});
Expand Down Expand Up @@ -589,9 +582,7 @@ public Task<Void> then(@Nullable AppSettingsData appSettingsData)
reportingCoordinator.sendReports(
appSettingsData.organizationId,
executor,
shouldSendViaDataTransport(appSettingsData.reportUploadVariant),
shouldSendViaDataTransport(
appSettingsData.nativeReportUploadVariant));
DataTransportState.getState(appSettingsData));
unsentReportsHandled.trySetResult(null);

return Tasks.forResult(null);
Expand All @@ -610,13 +601,11 @@ public ReportUploader createReportUploader(AppSettingsData appSettingsData) {
final String reportsUrl = appSettingsData.reportsUrl;
final String ndkReportsUrl = appSettingsData.ndkReportsUrl;
final String organizationId = appSettingsData.organizationId;
final boolean isUsingReportsEndpoint =
appSettingsData.reportUploadVariant != REPORT_UPLOAD_VARIANT_DATATRANSPORT;
final CreateReportSpiCall call = getCreateReportSpiCall(reportsUrl, ndkReportsUrl);
return new ReportUploader(
organizationId,
appData.googleAppId,
isUsingReportsEndpoint,
DataTransportState.getState(appSettingsData),
reportManager,
call,
handlingExceptionCheck);
Expand Down Expand Up @@ -1150,11 +1139,6 @@ private static String makeFirebaseSessionIdentifier(String sessionIdentifier) {
return sessionIdentifier.replaceAll("-", "");
}

private static SessionReportingCoordinator.SendReportPredicate shouldSendViaDataTransport(
int reportUploadVariant) {
return () -> REPORT_UPLOAD_VARIANT_DATATRANSPORT == reportUploadVariant;
}

// region Serialization to protobuf

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2020 Google LLC
//
// 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 com.google.firebase.crashlytics.internal.common;

import com.google.firebase.crashlytics.internal.settings.model.AppSettingsData;

public enum DataTransportState {
NONE,
JAVA_ONLY,
ALL;

// Used to determine whether to upload reports through the legacy reports endpoint
static final int REPORT_UPLOAD_VARIANT_LEGACY = 1;
// Used to determine whether to upload reports through the new DataTransport API.
static final int REPORT_UPLOAD_VARIANT_DATATRANSPORT = 2;

static DataTransportState getState(boolean dataTransportState, boolean dataTransportNativeState) {
if (!dataTransportState) {
return NONE;
}
if (!dataTransportNativeState) {
return JAVA_ONLY;
}
return ALL;
}

static DataTransportState getState(AppSettingsData appSettingsData) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test here?

final boolean dataTransportState =
appSettingsData.reportUploadVariant == REPORT_UPLOAD_VARIANT_DATATRANSPORT;
final boolean dataTransportNativeState =
appSettingsData.nativeReportUploadVariant == REPORT_UPLOAD_VARIANT_DATATRANSPORT;
return getState(dataTransportState, dataTransportNativeState);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@
*/
class SessionReportingCoordinator implements CrashlyticsLifecycleEvents {

public interface SendReportPredicate {
boolean shouldSendViaDataTransport();
}

private static final String EVENT_TYPE_CRASH = "crash";
private static final String EVENT_TYPE_LOGGED = "error";
private static final int EVENT_THREAD_IMPORTANCE = 4;
Expand Down Expand Up @@ -168,24 +164,22 @@ public void removeAllReports() {
* @param organizationId The organization ID this crash report should be associated with
* @param reportSendCompleteExecutor executor on which to run report cleanup after each report is
* sent.
* @param sendReportPredicate Predicate determining whether to send reports before cleaning them
* up
* @param dataTransportState
*/
public void sendReports(
String organizationId,
Executor reportSendCompleteExecutor,
SendReportPredicate sendReportPredicate,
SendReportPredicate sendNativeReportPredicate) {
if (!sendReportPredicate.shouldSendViaDataTransport()) {
DataTransportState dataTransportState) {
if (dataTransportState == DataTransportState.NONE) {
Logger.getLogger().d("Send via DataTransport disabled. Removing reports.");
reportPersistence.deleteAllReports();
return;
}
final List<CrashlyticsReportWithSessionId> reportsToSend =
reportPersistence.loadFinalizedReports();
for (CrashlyticsReportWithSessionId report : reportsToSend) {
if (report.getReport().getNdkPayload() != null
&& !sendNativeReportPredicate.shouldSendViaDataTransport()) {
if (report.getReport().getType() == CrashlyticsReport.Type.NATIVE
&& dataTransportState != DataTransportState.ALL) {
Logger.getLogger().d("Send native reports via DataTransport disabled. Removing reports.");
reportPersistence.deleteFinalizedReport(report.getSessionId());
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,29 @@ public abstract class CrashlyticsReport {
int UNKNOWN = 7;
}

public enum Type {
INCOMPLETE,
JAVA,
NATIVE
}

private static final Charset UTF_8 = Charset.forName("UTF-8");

@NonNull
public static Builder builder() {
return new AutoValue_CrashlyticsReport.Builder();
}

@Ignore
public Type getType() {
if (getSession() != null) {
return Type.JAVA;
} else if (getNdkPayload() != null) {
return Type.NATIVE;
}
return Type.INCOMPLETE;
}

@NonNull
public abstract String getSdkVersion();

Expand Down Expand Up @@ -131,7 +147,7 @@ public CrashlyticsReport withOrganizationId(@NonNull String organizationId) {
*/
@NonNull
public CrashlyticsReport withNdkPayload(@NonNull FilesPayload filesPayload) {
if (this.getSession() != null) {
if (getType() == Type.JAVA) {
throw new IllegalStateException("NDK Reports cannot have sessions within them.");
}

Expand Down
Loading