Skip to content

Commit 8d5572e

Browse files
committed
Submitter module
1 parent db95576 commit 8d5572e

39 files changed

+936
-372
lines changed

main/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ dependencies {
5656
api clientModuleProject('fallback')
5757
api clientModuleProject('backoff')
5858
api clientModuleProject('tracker')
59+
api clientModuleProject('submitter')
5960
// Internal module dependencies
6061
implementation clientModuleProject('http')
6162
implementation clientModuleProject('events-domain')

main/src/main/java/io/split/android/client/dtos/Event.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.google.gson.annotations.JsonAdapter;
44
import com.google.gson.annotations.SerializedName;
55

6-
import io.split.android.client.storage.common.InBytesSizable;
6+
import io.split.android.client.submitter.InBytesSizable;
77
import io.split.android.client.utils.deserializer.EventDeserializer;
88

99
@JsonAdapter(EventDeserializer.class)

main/src/main/java/io/split/android/client/dtos/KeyImpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.util.Objects;
77

88
import io.split.android.client.service.ServiceConstants;
9-
import io.split.android.client.storage.common.InBytesSizable;
9+
import io.split.android.client.submitter.InBytesSizable;
1010
import io.split.android.client.impressions.Impression;
1111

1212
public class KeyImpression implements InBytesSizable, Identifiable {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.split.android.client.service;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import io.split.android.client.service.http.HttpRecorder;
6+
import io.split.android.client.service.http.HttpRecorderException;
7+
import io.split.android.client.service.http.HttpStatus;
8+
import io.split.android.client.submitter.RecorderException;
9+
import io.split.android.client.submitter.RecorderSubmitter;
10+
11+
public class HttpRecorderSubmitterAdapter<T> implements RecorderSubmitter<T> {
12+
private final HttpRecorder<T> mHttpRecorder;
13+
14+
public HttpRecorderSubmitterAdapter(@NonNull HttpRecorder<T> httpRecorder) {
15+
mHttpRecorder = httpRecorder;
16+
}
17+
18+
@Override
19+
public void execute(@NonNull T data) throws RecorderException {
20+
try {
21+
mHttpRecorder.execute(data);
22+
} catch (HttpRecorderException e) {
23+
Integer httpStatus = e.getHttpStatus();
24+
boolean retryable = !HttpStatus.isNotRetryable(HttpStatus.fromCode(httpStatus));
25+
throw new RecorderException(e.getMessage(), httpStatus, retryable);
26+
}
27+
}
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.split.android.client.service;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import io.split.android.client.submitter.RecorderTelemetry;
6+
import io.split.android.client.telemetry.model.OperationType;
7+
import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer;
8+
9+
public class TelemetryRecorderAdapter implements RecorderTelemetry {
10+
private final TelemetryRuntimeProducer mTelemetryProducer;
11+
private final OperationType mOperationType;
12+
13+
public TelemetryRecorderAdapter(@NonNull TelemetryRuntimeProducer telemetryProducer,
14+
@NonNull OperationType operationType) {
15+
mTelemetryProducer = telemetryProducer;
16+
mOperationType = operationType;
17+
}
18+
19+
@Override
20+
public void recordSuccess(long timestamp) {
21+
mTelemetryProducer.recordSuccessfulSync(mOperationType, timestamp);
22+
}
23+
24+
@Override
25+
public void recordError(Integer httpStatus) {
26+
mTelemetryProducer.recordSyncError(mOperationType, httpStatus);
27+
}
28+
29+
@Override
30+
public void recordLatency(long latencyMs) {
31+
mTelemetryProducer.recordSyncLatency(mOperationType, latencyMs);
32+
}
33+
}
Lines changed: 15 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,37 @@
11
package io.split.android.client.service.events;
22

3-
import static io.split.android.client.utils.Utils.checkNotNull;
4-
import static io.split.android.client.utils.Utils.partition;
5-
63
import androidx.annotation.NonNull;
74

8-
import java.util.ArrayList;
9-
import java.util.Collections;
10-
import java.util.HashMap;
115
import java.util.List;
12-
import java.util.Map;
136

147
import io.split.android.client.dtos.Event;
15-
import io.split.android.client.service.executor.SplitTask;
16-
import io.split.android.client.service.executor.SplitTaskExecutionInfo;
17-
import io.split.android.client.service.executor.SplitTaskExecutionStatus;
8+
import io.split.android.client.service.HttpRecorderSubmitterAdapter;
9+
import io.split.android.client.service.TelemetryRecorderAdapter;
1810
import io.split.android.client.service.executor.SplitTaskType;
1911
import io.split.android.client.service.http.HttpRecorder;
20-
import io.split.android.client.service.http.HttpRecorderException;
21-
import io.split.android.client.service.http.HttpStatus;
2212
import io.split.android.client.storage.events.PersistentEventsStorage;
13+
import io.split.android.client.submitter.RecorderTask;
2314
import io.split.android.client.telemetry.model.OperationType;
2415
import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer;
25-
import io.split.android.client.utils.logger.Logger;
2616

27-
public class EventsRecorderTask implements SplitTask {
28-
public final static int FAILING_CHUNK_SIZE = 20;
29-
private final PersistentEventsStorage mPersistentEventsStorage;
30-
private final HttpRecorder<List<Event>> mHttpRecorder;
31-
private final EventsRecorderTaskConfig mConfig;
32-
private final TelemetryRuntimeProducer mTelemetryRuntimeProducer;
17+
public class EventsRecorderTask extends RecorderTask<Event, List<Event>> {
18+
19+
public static final int FAILING_CHUNK_SIZE = 20;
3320

3421
public EventsRecorderTask(@NonNull HttpRecorder<List<Event>> httpRecorder,
35-
@NonNull PersistentEventsStorage persistentEventsStorage,
22+
@NonNull PersistentEventsStorage storage,
3623
@NonNull EventsRecorderTaskConfig config,
3724
@NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) {
38-
mHttpRecorder = checkNotNull(httpRecorder);
39-
mPersistentEventsStorage = checkNotNull(persistentEventsStorage);
40-
mConfig = checkNotNull(config);
41-
mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer);
25+
super(storage,
26+
new HttpRecorderSubmitterAdapter<>(httpRecorder),
27+
config.getEventsPerPush(),
28+
SplitTaskType.EVENTS_RECORDER,
29+
new TelemetryRecorderAdapter(telemetryRuntimeProducer, OperationType.EVENTS),
30+
FAILING_CHUNK_SIZE);
4231
}
4332

4433
@Override
45-
@NonNull
46-
public SplitTaskExecutionInfo execute() {
47-
SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS;
48-
int nonSentRecords = 0;
49-
long nonSentBytes = 0;
50-
List<Event> events;
51-
List<Event> failingEvents = new ArrayList<>();
52-
boolean doNotRetry = false;
53-
do {
54-
events = mPersistentEventsStorage.pop(mConfig.getEventsPerPush());
55-
if (events.size() > 0) {
56-
long startTime = System.currentTimeMillis();
57-
long latency = 0;
58-
try {
59-
Logger.d("Posting %d Split events", events.size());
60-
mHttpRecorder.execute(events);
61-
62-
long now = System.currentTimeMillis();
63-
latency = now - startTime;
64-
mTelemetryRuntimeProducer.recordSuccessfulSync(OperationType.EVENTS, now);
65-
66-
mPersistentEventsStorage.delete(events);
67-
Logger.d("%d split events sent", events.size());
68-
} catch (HttpRecorderException e) {
69-
status = SplitTaskExecutionStatus.ERROR;
70-
nonSentRecords += mConfig.getEventsPerPush();
71-
nonSentBytes += sumEventBytes(events);
72-
Logger.e("Event recorder task: Some events couldn't be sent" +
73-
"Saving to send them in a new iteration: " +
74-
e.getLocalizedMessage());
75-
failingEvents.addAll(events);
76-
77-
mTelemetryRuntimeProducer.recordSyncError(OperationType.EVENTS, e.getHttpStatus());
78-
79-
if (HttpStatus.isNotRetryable(e.getHttpStatus())) {
80-
doNotRetry = true;
81-
break;
82-
}
83-
} finally {
84-
mTelemetryRuntimeProducer.recordSyncLatency(OperationType.EVENTS, latency);
85-
}
86-
}
87-
} while (events.size() == mConfig.getEventsPerPush());
88-
89-
// Update events by chunks to avoid sqlite errors
90-
List<List<Event>> failingChunks = partition(failingEvents, FAILING_CHUNK_SIZE);
91-
for (List<Event> chunk : failingChunks) {
92-
mPersistentEventsStorage.setActive(chunk);
93-
}
94-
95-
if (status == SplitTaskExecutionStatus.ERROR) {
96-
Map<String, Object> data = new HashMap<>();
97-
data.put(SplitTaskExecutionInfo.NON_SENT_RECORDS, nonSentRecords);
98-
data.put(SplitTaskExecutionInfo.NON_SENT_BYTES, nonSentBytes);
99-
if (doNotRetry) {
100-
data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true);
101-
}
102-
103-
return SplitTaskExecutionInfo.error(
104-
SplitTaskType.EVENTS_RECORDER, data);
105-
}
106-
return SplitTaskExecutionInfo.success(SplitTaskType.EVENTS_RECORDER);
107-
}
108-
109-
private long sumEventBytes(List<Event> events) {
110-
long totalBytes = 0;
111-
for (Event event : events) {
112-
totalBytes += event.getSizeInBytes();
113-
}
114-
return totalBytes;
34+
protected long estimateItemSize(Event item) {
35+
return item.getSizeInBytes();
11536
}
11637
}
Lines changed: 12 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,34 @@
11
package io.split.android.client.service.impressions;
22

3-
import static io.split.android.client.utils.Utils.checkNotNull;
4-
53
import androidx.annotation.NonNull;
64

7-
import java.util.ArrayList;
8-
import java.util.HashMap;
95
import java.util.List;
10-
import java.util.Map;
116

7+
import io.split.android.client.service.HttpRecorderSubmitterAdapter;
128
import io.split.android.client.service.ServiceConstants;
13-
import io.split.android.client.service.executor.SplitTask;
14-
import io.split.android.client.service.executor.SplitTaskExecutionInfo;
15-
import io.split.android.client.service.executor.SplitTaskExecutionStatus;
9+
import io.split.android.client.service.TelemetryRecorderAdapter;
1610
import io.split.android.client.service.executor.SplitTaskType;
1711
import io.split.android.client.service.http.HttpRecorder;
18-
import io.split.android.client.service.http.HttpRecorderException;
19-
import io.split.android.client.service.http.HttpStatus;
2012
import io.split.android.client.storage.impressions.PersistentImpressionsCountStorage;
13+
import io.split.android.client.submitter.RecorderTask;
2114
import io.split.android.client.telemetry.model.OperationType;
2215
import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer;
23-
import io.split.android.client.utils.logger.Logger;
2416

25-
public class ImpressionsCountRecorderTask implements SplitTask {
26-
private final PersistentImpressionsCountStorage mPersistentStorage;
27-
private final HttpRecorder<ImpressionsCount> mHttpRecorder;
28-
private static int POP_COUNT = ServiceConstants.DEFAULT_IMPRESSION_COUNT_ROWS_POP;
29-
private final TelemetryRuntimeProducer mTelemetryRuntimeProducer;
17+
public class ImpressionsCountRecorderTask extends RecorderTask<ImpressionsCountPerFeature, ImpressionsCount> {
3018

3119
public ImpressionsCountRecorderTask(@NonNull HttpRecorder<ImpressionsCount> httpRecorder,
3220
@NonNull PersistentImpressionsCountStorage persistentStorage,
3321
@NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) {
34-
mHttpRecorder = checkNotNull(httpRecorder);
35-
mPersistentStorage = checkNotNull(persistentStorage);
36-
mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer);
22+
super(persistentStorage,
23+
new HttpRecorderSubmitterAdapter<>(httpRecorder),
24+
ServiceConstants.DEFAULT_IMPRESSION_COUNT_ROWS_POP,
25+
SplitTaskType.IMPRESSIONS_COUNT_RECORDER,
26+
new TelemetryRecorderAdapter(telemetryRuntimeProducer, OperationType.IMPRESSIONS_COUNT),
27+
0);
3728
}
3829

3930
@Override
40-
@NonNull
41-
public SplitTaskExecutionInfo execute() {
42-
SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS;
43-
44-
List<ImpressionsCountPerFeature> countList = new ArrayList<>();
45-
List<ImpressionsCountPerFeature> failedSent = new ArrayList<>();
46-
boolean doNotRetry = false;
47-
do {
48-
countList = mPersistentStorage.pop(POP_COUNT);
49-
if (countList.size() > 0) {
50-
long startTime = System.currentTimeMillis();
51-
long latency = 0;
52-
try {
53-
Logger.d("Posting %d Split impressions count", countList.size());
54-
mHttpRecorder.execute(new ImpressionsCount(countList));
55-
56-
long now = System.currentTimeMillis();
57-
latency = now - startTime;
58-
mTelemetryRuntimeProducer.recordSuccessfulSync(OperationType.IMPRESSIONS_COUNT, now);
59-
60-
mPersistentStorage.delete(countList);
61-
Logger.d("%d split impressions count sent", countList.size());
62-
} catch (HttpRecorderException e) {
63-
status = SplitTaskExecutionStatus.ERROR;
64-
Logger.e("Impressions count recorder task: Some counts couldn't be sent. " +
65-
"Saving to send them in a new iteration\n" +
66-
e.getLocalizedMessage());
67-
failedSent.addAll(countList);
68-
69-
mTelemetryRuntimeProducer.recordSyncError(OperationType.IMPRESSIONS_COUNT, e.getHttpStatus());
70-
71-
if (HttpStatus.isNotRetryable(HttpStatus.fromCode(e.getHttpStatus()))) {
72-
doNotRetry = true;
73-
break;
74-
}
75-
} finally {
76-
mTelemetryRuntimeProducer.recordSyncLatency(OperationType.IMPRESSIONS_COUNT, latency);
77-
}
78-
}
79-
} while (countList.size() == POP_COUNT);
80-
81-
if (failedSent.size() > 0) {
82-
mPersistentStorage.setActive(failedSent);
83-
}
84-
85-
if (status == SplitTaskExecutionStatus.ERROR) {
86-
Map<String, Object> data = new HashMap<>();
87-
if (doNotRetry) {
88-
data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true);
89-
}
90-
91-
return SplitTaskExecutionInfo.error(SplitTaskType.IMPRESSIONS_COUNT_RECORDER, data);
92-
}
93-
94-
return SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_COUNT_RECORDER);
31+
protected ImpressionsCount transformForSubmission(List<ImpressionsCountPerFeature> items) {
32+
return new ImpressionsCount(items);
9533
}
9634
}

0 commit comments

Comments
 (0)