forked from mozilla/glean
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DO NOT MERGE] prototype to use a rust experiments api and expose it …
…through glean
- Loading branch information
Tarik Eshaq
committed
Jul 22, 2020
1 parent
200f9d3
commit c243d5e
Showing
12 changed files
with
661 additions
and
12 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
libraryVersion: 31.4.0 | ||
libraryVersion: 31.4.0-TESTING28 | ||
groupId: org.mozilla.telemetry | ||
projects: | ||
glean: | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
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
114 changes: 114 additions & 0 deletions
114
glean-core/android/src/main/java/mozilla/telemetry/glean/Experiments.kt
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,114 @@ | ||
package mozilla.telemetry.glean | ||
|
||
import android.content.Context | ||
import android.os.Build | ||
import com.sun.jna.Pointer | ||
import mozilla.telemetry.glean.rust.LibGleanFFI | ||
import mozilla.telemetry.glean.rust.RustError | ||
import java.util.* | ||
import java.util.concurrent.atomic.AtomicLong | ||
import com.google.protobuf.CodedOutputStream | ||
import com.google.protobuf.MessageLite | ||
import com.sun.jna.Native | ||
import java.nio.ByteBuffer | ||
import java.nio.ByteOrder | ||
|
||
// A LOT OF THIS IS COPIED FOR THE SAKE OF THE PROTOTYPE, NOT COMPLETE | ||
fun <T : MessageLite> T.toNioDirectBuffer(): Pair<ByteBuffer, Int> { | ||
val len = this.serializedSize | ||
val nioBuf = ByteBuffer.allocateDirect(len) | ||
nioBuf.order(ByteOrder.nativeOrder()) | ||
val output = CodedOutputStream.newInstance(nioBuf) | ||
this.writeTo(output) | ||
output.checkNoSpaceLeft() | ||
return Pair(first = nioBuf, second = len) | ||
} | ||
|
||
open class ExperimentsInternalAPI internal constructor () { | ||
private var raw: AtomicLong = AtomicLong(0) | ||
|
||
fun initialize( | ||
applicationContext: Context, | ||
dbPath: String | ||
) { | ||
val appCtx = MsgTypes.AppContext.newBuilder() | ||
.setAppId(applicationContext.packageName) | ||
.setAppVersion(applicationContext.packageManager.getPackageInfo(applicationContext.packageName, 0).versionName) | ||
.setDeviceManufacturer(Build.MANUFACTURER) | ||
.setLocaleCountry( | ||
try { | ||
Locale.getDefault().isO3Country | ||
} catch (e: MissingResourceException) { | ||
Locale.getDefault().country | ||
} | ||
) | ||
.setLocaleLanguage( | ||
try { | ||
Locale.getDefault().isO3Language | ||
} catch (e: MissingResourceException) { | ||
Locale.getDefault().language | ||
} | ||
) | ||
.setDeviceModel(Build.DEVICE) | ||
.build() | ||
val (nioBuf, len) = appCtx.toNioDirectBuffer() | ||
raw.set( rustCall { error -> | ||
val ptr = Native.getDirectBufferPointer(nioBuf) | ||
LibGleanFFI.INSTANCE.experiments_new(ptr, len, dbPath, error) | ||
}) | ||
} | ||
|
||
fun getBranch(experimentName: String): String { | ||
var ptr = rustCall { error -> | ||
LibGleanFFI.INSTANCE.experiments_get_branch(raw.get(), experimentName, error) | ||
} | ||
return ptr.getAndConsumeRustString() | ||
} | ||
|
||
/** | ||
* Helper to read a null terminated String out of the Pointer and free it. | ||
* | ||
* Important: Do not use this pointer after this! For anything! | ||
*/ | ||
internal fun Pointer.getAndConsumeRustString(): String { | ||
return this.getRustString() | ||
// PLEASE INSERT A FREE HERE!!!!!!! | ||
} | ||
|
||
/** | ||
* Helper to read a null terminated string out of the pointer. | ||
* | ||
* Important: doesn't free the pointer, use [getAndConsumeRustString] for that! | ||
*/ | ||
internal fun Pointer.getRustString(): String { | ||
return this.getString(0, "utf8") | ||
} | ||
|
||
// In practice we usually need to be synchronized to call this safely, so it doesn't | ||
// synchronize itself | ||
private inline fun <U> nullableRustCall(callback: (RustError.ByReference) -> U?): U? { | ||
val e = RustError.ByReference() | ||
try { | ||
val ret = callback(e) | ||
if (e.isFailure()) { | ||
// We ignore it for now, although we shouldn't just cuz protoype | ||
//throw e.intoException() | ||
} | ||
return ret | ||
} finally { | ||
// This only matters if `callback` throws (or does a non-local return, which | ||
// we currently don't do) | ||
e.ensureConsumed() | ||
} | ||
} | ||
|
||
private inline fun <U> rustCall(callback: (RustError.ByReference) -> U?): U { | ||
return nullableRustCall(callback)!! | ||
} | ||
} | ||
|
||
/** | ||
* The main experiments object | ||
* ``` | ||
*/ | ||
object Experiments : ExperimentsInternalAPI() |
150 changes: 150 additions & 0 deletions
150
glean-core/android/src/main/java/mozilla/telemetry/glean/HttpConfig.kt
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,150 @@ | ||
package mozilla.telemetry.glean | ||
|
||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
// THIS IS A CLONE OF THE OTHER HTTPCONFIG USED FOR APPLICATION SERVICES | ||
// SEEMED CONVIENIENT FOR THE SAKE OF THE PROTOTYPE, PREFERRABLY | ||
// WE CAN USE THE SAME HTTPCONFIG, BUT I COULDN'T WRAP MY HEAD ON A QUICK | ||
// WAY TO DO THAT FOR A PROTOTYPE | ||
|
||
import com.google.protobuf.ByteString | ||
import mozilla.components.concept.fetch.Client | ||
import mozilla.components.concept.fetch.MutableHeaders | ||
import mozilla.components.concept.fetch.Request | ||
import mozilla.telemetry.glean.rust.LibGleanFFI | ||
import mozilla.telemetry.glean.rust.RawFetchCallback | ||
import mozilla.telemetry.glean.rust.RustBuffer | ||
import java.util.concurrent.TimeUnit | ||
import java.util.concurrent.locks.ReentrantReadWriteLock | ||
import kotlin.concurrent.read | ||
import kotlin.concurrent.write | ||
|
||
/** | ||
* Singleton allowing management of the HTTP backend | ||
* used by Rust components. | ||
*/ | ||
object RustHttpConfig { | ||
// Protects imp/client | ||
private var lock = ReentrantReadWriteLock() | ||
@Volatile | ||
private var client: Lazy<Client>? = null | ||
// Important note to future maintainers: if you mess around with | ||
// this code, you have to make sure `imp` can't get GCed. Extremely | ||
// bad things will happen if it does! | ||
@Volatile | ||
private var imp: CallbackImpl? = null | ||
|
||
/** | ||
* Set the HTTP client to be used by all Rust code. | ||
* the `Lazy`'s value is not read until the first request is made. | ||
*/ | ||
@Synchronized | ||
fun setClient(c: Lazy<Client>) { | ||
lock.write { | ||
client = c | ||
if (imp == null) { | ||
imp = CallbackImpl() | ||
LibGleanFFI.INSTANCE.viaduct_initialize(imp!!) | ||
} | ||
} | ||
} | ||
|
||
internal fun convertRequest(request: MsgTypes.Request): Request { | ||
val headers = MutableHeaders() | ||
for (h in request.headersMap) { | ||
headers.append(h.key, h.value) | ||
} | ||
return Request( | ||
url = request.url, | ||
method = convertMethod(request.method), | ||
headers = headers, | ||
connectTimeout = Pair(request.connectTimeoutSecs.toLong(), TimeUnit.SECONDS), | ||
readTimeout = Pair(request.readTimeoutSecs.toLong(), TimeUnit.SECONDS), | ||
body = if (request.hasBody()) { | ||
Request.Body(request.body.newInput()) | ||
} else { | ||
null | ||
}, | ||
redirect = if (request.followRedirects) { | ||
Request.Redirect.FOLLOW | ||
} else { | ||
Request.Redirect.MANUAL | ||
}, | ||
cookiePolicy = Request.CookiePolicy.OMIT, | ||
useCaches = request.useCaches | ||
) | ||
} | ||
|
||
@Suppress("TooGenericExceptionCaught", "ReturnCount") | ||
internal fun doFetch(b: RustBuffer.ByValue): RustBuffer.ByValue { | ||
lock.read { | ||
try { | ||
val request = MsgTypes.Request.parseFrom(b.asCodedInputStream()) | ||
val rb = try { | ||
// Note: `client!!` is fine here, since if client is null, | ||
// we wouldn't have yet initialized | ||
val resp = client!!.value.fetch(convertRequest(request)) | ||
val rb = MsgTypes.Response.newBuilder() | ||
.setUrl(resp.url) | ||
.setStatus(resp.status) | ||
.setBody(resp.body.useStream { | ||
ByteString.readFrom(it) | ||
}) | ||
|
||
for (h in resp.headers) { | ||
rb.putHeaders(h.name, h.value) | ||
} | ||
rb | ||
} catch (e: Throwable) { | ||
MsgTypes.Response.newBuilder().setExceptionMessage("fetch error: ${e.message ?: e.javaClass.canonicalName}") | ||
} | ||
val built = rb.build() | ||
val needed = built.serializedSize | ||
val outputBuf = LibGleanFFI.INSTANCE.viaduct_alloc_bytebuffer(needed) | ||
try { | ||
// This is only null if we passed a negative number or something to | ||
// viaduct_alloc_bytebuffer. | ||
val stream = outputBuf.asCodedOutputStream()!! | ||
built.writeTo(stream) | ||
return outputBuf | ||
} catch (e: Throwable) { | ||
// Note: we want to clean this up only if we are not returning it to rust. | ||
LibGleanFFI.INSTANCE.viaduct_destroy_bytebuffer(outputBuf) | ||
LibGleanFFI.INSTANCE.viaduct_log_error("Failed to write buffer: ${e.message}") | ||
throw e | ||
} | ||
} finally { | ||
LibGleanFFI.INSTANCE.viaduct_destroy_bytebuffer(b) | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal fun convertMethod(m: MsgTypes.Request.Method): Request.Method { | ||
return when (m) { | ||
MsgTypes.Request.Method.GET -> Request.Method.GET | ||
MsgTypes.Request.Method.POST -> Request.Method.POST | ||
MsgTypes.Request.Method.HEAD -> Request.Method.HEAD | ||
MsgTypes.Request.Method.OPTIONS -> Request.Method.OPTIONS | ||
MsgTypes.Request.Method.DELETE -> Request.Method.DELETE | ||
MsgTypes.Request.Method.PUT -> Request.Method.PUT | ||
MsgTypes.Request.Method.TRACE -> Request.Method.TRACE | ||
MsgTypes.Request.Method.CONNECT -> Request.Method.CONNECT | ||
} | ||
} | ||
|
||
internal class CallbackImpl : RawFetchCallback { | ||
@Suppress("TooGenericExceptionCaught") | ||
override fun invoke(b: RustBuffer.ByValue): RustBuffer.ByValue { | ||
try { | ||
return RustHttpConfig.doFetch(b) | ||
} catch (e: Throwable) { | ||
LibGleanFFI.INSTANCE.viaduct_log_error("doFetch failed: ${e.message}") | ||
// This is our last resort. It's bad news should we fail to | ||
// return something from this function. | ||
return RustBuffer.ByValue() | ||
} | ||
} | ||
} |
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
Oops, something went wrong.