Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
some refactoring to improve testability / minimize the use of globals
- Loading branch information
Showing
30 changed files
with
807 additions
and
458 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ buildscript { | |
} | ||
|
||
ext { | ||
theVersionName = '0.1.2' | ||
theVersionName = '0.1.3' | ||
} | ||
|
||
allprojects { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Performance | ||
RxTracer uses preemptive stack traces to provide call-site details when an exception occurs. | ||
|
||
While capturing a stack trace is a relatively slow operation, a trace is captured only once per subscription and | ||
subscription tends to be an infrequent operation: You create an observable, you subscribe to it and you operate | ||
on it's stream of emissions. There are many emissions, but only one subscription*. | ||
|
||
_\* It has been pointed out that operations such as `flatMap` result in additional subscriptions. This is true but | ||
point about the ratio between subscriptions and emissions still holds. I've added a benchmark below to | ||
capture this scenario._ | ||
|
||
Use cases do exist where hundreds or more subscriptions are firing every second. If | ||
your project falls into that category then you'll want measure the performance impact of using rxtracer. | ||
For most software projects, particularly mobile apps, desktop apps and and apps not running on a server, | ||
the overhead is typically negligible. | ||
|
||
These sorts of assertions are notoriously difficult to prove one way or another, but I'll attempt to | ||
use a Caliper microbenchmark to provide some baseline numbers: | ||
|
||
For those interested, the source of the microbenchmark is [available here](rxtracer/src/test/java/com/halfhp/rxtracer/RxTracerBenchmark.java). | ||
|
||
I ran the following benchmarks on a 2016 Macbook Pro with a 2.9ghz Intel i7 CPU. | ||
|
||
The first benchmark, `measureInstantiateOnly`, measures only the time taken to instantiate a new Observable. | ||
Most of the heavy lifting is done at this stage so these results paint RxTracer in | ||
the worst possible light. | ||
|
||
* **DISABLED:** 7.8ns per instantiation | ||
* **ENABLED:** 14.2ns per instantiation | ||
|
||
You'll almost never instantiate an observable without subscribing to it though, since that would be pointless. As a | ||
real-world example the second benchmark `measureInstantiatePlusSubscibe` measures the combined time of instantiation and subscription: | ||
|
||
* **DISABLED:** 53ns per instantiate-subscribe | ||
* **ENABLED:** 62ns per instantiate-subscribe | ||
|
||
Practically nothing is happening in the body of subscribe and we're still only | ||
looking at a overhead of about 17%. In most real-world scenarios we're very likely looking at less than 10%. | ||
|
||
The final benchmark `measureLongFlatMapSubscribeChain` instantiates an observable, runs it through five successive | ||
invocations of `flatMap` and then subscribes to the result. | ||
|
||
* **DISABLED** 295.38μs per instantiate-flatmap-5x-susbscribe | ||
* **ENABLED** 313.75μs per instantiate-flatmap-5x-susbscribe | ||
|
||
While both benchmarks shoot up into the microseconds range (possibly due to me misusing Caliper), the measured overhead | ||
of RxTracer is roughly 6%. Five successive `flatMap` invocations might be a tad high to be representative of an average case, | ||
but keep in mind that each invocation is doing the bare minimum of work. | ||
|
||
## RxTracer vs Traceur | ||
The [benchmarks](rxtracer/src/test/java/com/halfhp/rxtracer/RxTracerBenchmark.java) include a Traceur profile for each of the above tests. | ||
While the benchmarks show RxTracer is about 15% faster than Traceur on average. |
41 changes: 41 additions & 0 deletions
41
rxtracer/src/main/java/com/halfhp/rxtracer/AssemblyWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.halfhp.rxtracer; | ||
|
||
import io.reactivex.annotations.NonNull; | ||
import io.reactivex.annotations.Nullable; | ||
import io.reactivex.functions.Function; | ||
|
||
/** | ||
* Wraps an existing rx assembly so that it can be invoked after rxtracer's own assembly. | ||
* @param <T> | ||
*/ | ||
class AssemblyWrapper<T> implements Function<T, T> { | ||
private final Function<? super T, ? extends T> wrappedAssembly; | ||
private final Function<? super T, ? extends T> tracer; | ||
|
||
private AssemblyWrapper( | ||
@NonNull Function<? super T, ? extends T> tracer, | ||
@Nullable Function<? super T, ? extends T> wrappedAssembly) { | ||
this.tracer = tracer; | ||
this.wrappedAssembly = wrappedAssembly; | ||
} | ||
|
||
@Nullable | ||
Function<? super T, ? extends T> getWrappedAssembly() { | ||
return wrappedAssembly; | ||
} | ||
|
||
@Override | ||
public T apply(T t) throws Exception { | ||
if (wrappedAssembly != null) { | ||
return wrappedAssembly.apply(tracer.apply(t)); | ||
} else { | ||
return tracer.apply(t); | ||
} | ||
} | ||
|
||
static <T> Function<? super T, ? extends T> wrap( | ||
@NonNull Function<? super T, ? extends T> tracer, | ||
@Nullable Function<? super T, ? extends T> wrappedAssembly) { | ||
return new AssemblyWrapper<>(tracer, wrappedAssembly); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
rxtracer/src/main/java/com/halfhp/rxtracer/CompletableObserverWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.halfhp.rxtracer; | ||
|
||
import io.reactivex.CompletableObserver; | ||
import io.reactivex.annotations.NonNull; | ||
import io.reactivex.disposables.Disposable; | ||
|
||
public class CompletableObserverWrapper extends TracingObserver<CompletableObserver> implements CompletableObserver { | ||
|
||
public CompletableObserverWrapper(@NonNull CompletableObserver wrapped, @NonNull StackTraceRewriter rewriter) { | ||
super(wrapped, rewriter); | ||
} | ||
|
||
@Override | ||
public void onSubscribe(Disposable d) { | ||
wrapped.onSubscribe(d); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
wrapped.onComplete(); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable t) { | ||
wrapped.onError(rewriter.rewrite(t, this.stackTrace)); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
rxtracer/src/main/java/com/halfhp/rxtracer/FlowableSubscriberWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.halfhp.rxtracer; | ||
|
||
import io.reactivex.annotations.NonNull; | ||
import org.reactivestreams.Subscriber; | ||
import org.reactivestreams.Subscription; | ||
|
||
public class FlowableSubscriberWrapper<T> extends TracingObserver<Subscriber<? super T>> implements Subscriber<T> { | ||
|
||
public FlowableSubscriberWrapper(@NonNull Subscriber<? super T> wrapped, @NonNull StackTraceRewriter rewriter) { | ||
super(wrapped, rewriter); | ||
} | ||
|
||
@Override | ||
public void onSubscribe(Subscription s) { | ||
wrapped.onSubscribe(s); | ||
} | ||
|
||
@Override | ||
public void onNext(T t) { | ||
wrapped.onNext(t); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable e) { | ||
wrapped.onError(rewriter.rewrite(e, this.stackTrace)); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
wrapped.onComplete(); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
rxtracer/src/main/java/com/halfhp/rxtracer/MaybeObserverWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.halfhp.rxtracer; | ||
|
||
import io.reactivex.MaybeObserver; | ||
import io.reactivex.annotations.NonNull; | ||
import io.reactivex.disposables.Disposable; | ||
|
||
public class MaybeObserverWrapper<T> extends TracingObserver<MaybeObserver<? super T>> implements MaybeObserver<T> { | ||
|
||
public MaybeObserverWrapper(@NonNull MaybeObserver<? super T> wrapped, @NonNull StackTraceRewriter rewriter) { | ||
super(wrapped, rewriter); | ||
} | ||
|
||
@Override | ||
public void onSubscribe(Disposable d) { | ||
wrapped.onSubscribe(d); | ||
} | ||
|
||
@Override | ||
public void onSuccess(T t) { | ||
wrapped.onSuccess(t); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable e) { | ||
wrapped.onError(rewriter.rewrite(e, this.stackTrace)); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
wrapped.onComplete(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.halfhp.rxtracer; | ||
|
||
/** | ||
* | ||
*/ | ||
public enum Mode { | ||
/** | ||
* The default mode. Completely replaces the stack trace of the initial exception thrown | ||
* from within a subscribe callback with the stack trace that was recorded when subscribe was originally invoked. | ||
* This is typically the most appropriate and expected approach. | ||
*/ | ||
REWRITE, | ||
|
||
/** | ||
* Append the the stack trace recorded when subscribe was invoked to the stack trace of the | ||
* exception generated when the subscribe callback ran | ||
*/ | ||
APPEND, | ||
|
||
/** | ||
* Experimental - prepend the the stack trace recorded when subscribe was invoked to the stack trace of the | ||
* exception generated when the subscribe callback ran. This approach can sometimes produce | ||
* better stack trace aggregation in bug trackers like Crashalytics etc. | ||
*/ | ||
PREPEND | ||
} |
32 changes: 32 additions & 0 deletions
32
rxtracer/src/main/java/com/halfhp/rxtracer/ObserverWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.halfhp.rxtracer; | ||
|
||
import io.reactivex.Observer; | ||
import io.reactivex.annotations.NonNull; | ||
import io.reactivex.disposables.Disposable; | ||
|
||
public class ObserverWrapper<T> extends TracingObserver<Observer<? super T>> implements Observer<T> { | ||
|
||
public ObserverWrapper(@NonNull Observer<? super T> wrapped, @NonNull StackTraceRewriter rewriter) { | ||
super(wrapped, rewriter); | ||
} | ||
|
||
@Override | ||
public void onSubscribe(Disposable d) { | ||
wrapped.onSubscribe(d); | ||
} | ||
|
||
@Override | ||
public void onNext(T t) { | ||
wrapped.onNext(t); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable t) { | ||
wrapped.onError(rewriter.rewrite(t, this.stackTrace)); | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
wrapped.onComplete(); | ||
} | ||
} |
Oops, something went wrong.