-
Notifications
You must be signed in to change notification settings - Fork 0
Experiment init #1
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
Open
tarikeshaq
wants to merge
2
commits into
main
Choose a base branch
from
experiment-init
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,34 @@ | ||
[package] | ||
name = "nimbus_experiments" | ||
version = "0.1.0" | ||
authors = ["Tarik Eshaq <[email protected]>"] | ||
edition = "2018" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
url = "2.1" | ||
serde = { version = "1", features = ["rc"] } | ||
serde_derive = "1" | ||
serde_json = "1" | ||
anyhow = "1.0" | ||
rand = "0.7" | ||
log = "0.4" | ||
viaduct = { git = "https://github.com/mozilla/application-services" } | ||
ffi-support = "0.4" | ||
thiserror = "1" | ||
rkv = "0.10" | ||
lazy_static = "1.4" | ||
uuid = { version = "0.8", features = ["serde", "v4"]} | ||
prost = "0.6" | ||
|
||
[build-dependencies] | ||
prost-build = { version = "0.6" } | ||
|
||
[lib] | ||
name = "nimbus_experiments" | ||
crate-type = ["lib"] | ||
|
||
[dev-dependencies] | ||
viaduct-reqwest = { git = "https://github.com/mozilla/application-services" } | ||
|
This file contains hidden or 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,7 @@ | ||
/* 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/. */ | ||
|
||
fn main() { | ||
prost_build::compile_protos(&["src/experiments_msg_types.proto"], &["src/"]).unwrap(); | ||
} |
This file contains hidden or 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,24 @@ | ||
use anyhow::Result; | ||
use nimbus_experiments::{AppContext, Experiments}; | ||
fn main() -> Result<()> { | ||
viaduct_reqwest::use_reqwest_backend(); | ||
let exp = Experiments::new(AppContext::default(), "./mydb"); | ||
let enrolled_exp = exp.get_enrolled_experiments(); | ||
exp.get_experiments().iter().for_each(|e| { | ||
print!( | ||
"Experiment: \"{}\", Buckets: {} to {}, Branches: ", | ||
e.id, e.buckets.count, e.buckets.start | ||
); | ||
e.branches.iter().for_each(|b| print!(" \"{}\", ", b.name)); | ||
println!() | ||
}); | ||
println!("You are in bucket: {}", exp.get_bucket()); | ||
enrolled_exp.iter().for_each(|ee| { | ||
println!( | ||
"Enrolled in experiment \"{}\" in branch \"{}\"", | ||
ee.get_id(), | ||
ee.get_branch() | ||
) | ||
}); | ||
Ok(()) | ||
} |
This file contains hidden or 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,9 @@ | ||
/* 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 might be where the bucketing logic can go | ||
//! It would be different from current experimentation tools | ||
//! There is a namespacing concept to allow users to be in multiple | ||
//! unrelated experiments at the same time. | ||
//! Not implemented yet |
This file contains hidden or 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,29 @@ | ||
/* 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/. */ | ||
|
||
//! Not implemented yet!!! | ||
//! This is purely boilerplate to communicate over the ffi | ||
//! We should define real variants for our error and use proper | ||
//! error propegation (we can use the `thiserror` crate for that) | ||
use ffi_support::{ErrorCode, ExternError}; | ||
#[derive(Debug, thiserror::Error)] | ||
pub enum Error { | ||
#[error("Invalid")] | ||
Invalid, | ||
} | ||
|
||
pub type Result<T, E = Error> = std::result::Result<T, E>; | ||
|
||
impl From<Error> for ExternError { | ||
fn from(_: Error) -> ExternError { | ||
let code = ErrorCode::new(1); | ||
ExternError::new_error(code, "UNEXPECTED") | ||
} | ||
} | ||
|
||
impl Into<Error> for anyhow::Error { | ||
fn into(self) -> Error { | ||
Error::Invalid | ||
} | ||
} |
This file contains hidden or 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,9 @@ | ||
# This is a test file for defining WebIDL for uniffi | ||
# For the time being, it is not used for anything! | ||
# However, if we use uniffi in the future, we could define | ||
# The api here. (Unless uniffi changes to a non WebIDL way (looking at you proc-macros)) | ||
namespace experiments {}; | ||
interface Experiments { | ||
constructor(); | ||
void get_experiment_branch(); | ||
}; |
This file contains hidden or 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,24 @@ | ||
syntax = "proto2"; | ||
|
||
// This kinda beats the purpose of using protobufs since we have one file here/ | ||
// And a duplicate in the glean PR, but bear with me :) | ||
// Eventually once we figure out the details of where each part lives, we'll merge the proto files | ||
// into one | ||
|
||
package mozilla.telemetry.glean.protobuf; | ||
|
||
option java_package = "mozilla.telemtery.glean"; | ||
option java_outer_classname = "MsgTypes"; | ||
option swift_prefix = "MsgTypes_"; | ||
option optimize_for = LITE_RUNTIME; | ||
|
||
message AppContext { | ||
optional string app_id = 1; | ||
optional string app_version = 2; | ||
optional string locale_language = 3; | ||
optional string locale_country = 4; | ||
optional string device_manufacturer = 5; | ||
optional string device_model = 6; | ||
optional string region = 7; | ||
optional string debug_tag = 8; | ||
} |
This file contains hidden or 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,69 @@ | ||
/* 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/. */ | ||
|
||
use std::os::raw::c_char; | ||
|
||
use super::{error::Result, msg_types, AppContext, Experiments}; | ||
use ffi_support::{define_handle_map_deleter, ConcurrentHandleMap, ExternError, FfiStr}; | ||
|
||
lazy_static::lazy_static! { | ||
static ref EXPERIMENTS: ConcurrentHandleMap<Experiments> = ConcurrentHandleMap::new(); | ||
} | ||
|
||
#[no_mangle] | ||
pub extern "C" fn experiments_new( | ||
app_ctx: *const u8, | ||
app_ctx_len: i32, | ||
db_path: FfiStr<'_>, | ||
error: &mut ExternError, | ||
) -> u64 { | ||
EXPERIMENTS.insert_with_result(error, || -> Result<Experiments> { | ||
let app_ctx = unsafe { | ||
from_protobuf_ptr::<AppContext, msg_types::AppContext>(app_ctx, app_ctx_len).unwrap() | ||
}; // Todo: make the whole function unsafe and implement proper error handling in error.rs | ||
log::info!("=================== Initializing experiments ========================"); | ||
Ok(Experiments::new(app_ctx, db_path.as_str())) | ||
}) | ||
} | ||
|
||
#[no_mangle] | ||
pub extern "C" fn experiments_get_branch( | ||
handle: u64, | ||
branch: FfiStr<'_>, | ||
error: &mut ExternError, | ||
) -> *mut c_char { | ||
EXPERIMENTS.call_with_result(error, handle, |experiment| -> Result<String> { | ||
log::info!("==================== Getting branch ========================"); | ||
let branch_name = experiment.get_experiment_branch(branch.as_str())?; | ||
Ok(branch_name) | ||
}) | ||
} | ||
|
||
define_handle_map_deleter!(EXPERIMENTS, experiements_destroy); | ||
|
||
/// # Safety | ||
/// data is a raw pointer to the protobuf data | ||
/// get_buffer will return an error if the length is invalid, | ||
/// or if the pointer is a null pointer | ||
pub unsafe fn from_protobuf_ptr<T, F: prost::Message + Default + Into<T>>( | ||
data: *const u8, | ||
len: i32, | ||
) -> anyhow::Result<T> { | ||
let buffer = get_buffer(data, len)?; | ||
let item: Result<F, _> = prost::Message::decode(buffer); | ||
item.map(|inner| inner.into()).map_err(|e| e.into()) | ||
} | ||
|
||
unsafe fn get_buffer<'a>(data: *const u8, len: i32) -> anyhow::Result<&'a [u8]> { | ||
match len { | ||
len if len < 0 => anyhow::bail!("Invalid length"), | ||
0 => Ok(&[]), | ||
_ => { | ||
if data.is_null() { | ||
anyhow::bail!("Null pointer") | ||
} | ||
Ok(std::slice::from_raw_parts(data, len as usize)) | ||
} | ||
} | ||
} |
This file contains hidden or 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,81 @@ | ||
/* 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 simple Http client that uses viaduct to retrieve experiment data from the server | ||
//! Currently configured to use Kinto and the old schema, although that would change once we start | ||
//! Working on the real Nimbus schema. | ||
|
||
use super::Experiment; | ||
use anyhow::Result; | ||
use serde_derive::*; | ||
use url::Url; | ||
use viaduct::{status_codes, Request, Response}; | ||
|
||
// Making this a trait so that we can mock those later. | ||
pub(crate) trait SettingsClient { | ||
fn get_experiements_metadata(&self) -> Result<String>; | ||
fn get_experiments(&self) -> Result<Vec<Experiment>>; | ||
} | ||
|
||
#[derive(Deserialize)] | ||
struct RecordsResponse { | ||
data: Vec<Experiment>, | ||
} | ||
|
||
pub struct Client { | ||
base_url: Url, | ||
collection_name: String, | ||
bucket_name: String, | ||
} | ||
|
||
impl Client { | ||
pub fn new(base_url: Url, collection_name: String, bucket_name: String) -> Self { | ||
Self { | ||
base_url, | ||
collection_name, | ||
bucket_name, | ||
} | ||
} | ||
|
||
fn make_request(&self, request: Request) -> Result<Response> { | ||
let resp = request.send()?; | ||
if resp.is_success() || resp.status == status_codes::NOT_MODIFIED { | ||
Ok(resp) | ||
} else { | ||
anyhow::bail!("Error in request: {}", resp.text()) | ||
} | ||
} | ||
} | ||
|
||
impl SettingsClient for Client { | ||
fn get_experiements_metadata(&self) -> Result<String> { | ||
let path = format!( | ||
"buckets/{}/collections/{}", | ||
&self.bucket_name, &self.collection_name | ||
); | ||
let url = self.base_url.join(&path)?; | ||
let req = Request::get(url).header( | ||
"User-Agent", | ||
"Experiments Rust Component <[email protected]>", | ||
)?; | ||
let resp = self.make_request(req)?; | ||
let res = serde_json::to_string(&resp.body)?; | ||
Ok(res) | ||
} | ||
|
||
fn get_experiments(&self) -> Result<Vec<Experiment>> { | ||
let path = format!( | ||
"buckets/{}/collections/{}/records", | ||
&self.bucket_name, &self.collection_name | ||
); | ||
let url = self.base_url.join(&path)?; | ||
let req = Request::get(url).header( | ||
"User-Agent", | ||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0", | ||
)?; | ||
// TODO: Add authentication based on server requirements | ||
let resp = self.make_request(req)?.json::<RecordsResponse>()?; | ||
Ok(resp.data) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tarikeshaq I am not sure if they have authentication !