Skip to content

Commit

Permalink
Initial implementation of #254
Browse files Browse the repository at this point in the history
- Can currently set the `telemetry` flag.
- Telemetry status is stored in `MULTIRUST_HOME/telemetry`
- Adds routing for telemetry for rustc calls and target add
- Provides basic command proxying for telemetry
  • Loading branch information
peschkaj committed Apr 18, 2016
1 parent 92eac2f commit a5128e8
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 8 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tempdir = "0.3.4"
libc = "0.2.0"
rand = "0.3.11"
scopeguard = "0.1.2"
rustc-serialize = "0.3"

[target.x86_64-pc-windows-gnu.dependencies]
winapi = "0.2.4"
Expand Down
109 changes: 109 additions & 0 deletions src/multirust/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std;
use std::env;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{self, Command, Output};
use std::time::Instant;

use Cfg;
use errors::*;
use multirust_utils;
use telemetry;
use telemetry::TelemetryEvent;


pub fn run_command_for_dir<S: AsRef<OsStr>>(cmd: Command,
args: &[S],
cfg: &Cfg) -> Result<()> {
let arg0 = env::args().next().map(|a| PathBuf::from(a));
let arg0 = arg0.as_ref()
.and_then(|a| a.file_name())
.and_then(|a| a.to_str());
let arg0 = try!(arg0.ok_or(Error::NoExeName));
if (arg0 == "rustc" || arg0 == "rustc.exe") && cfg.telemetry_enabled() {
return telemetry_rustc(cmd, &args, &cfg);
}

run_command_for_dir_without_telemetry(cmd, &args)
}

fn telemetry_rustc<S: AsRef<OsStr>>(cmd: Command, args: &[S], cfg: &Cfg) -> Result<()> {
let now = Instant::now();

let output = bare_run_command_for_dir(cmd, &args);

let duration = now.elapsed();

let ms = (duration.as_secs() as u64 * 1000) + (duration.subsec_nanos() as u64 / 1000 / 1000);

match output {
Ok(out) => {
let exit_code = out.status.code().unwrap_or(1);

let errors = match out.status.success() {
true => None,
_ => Some(String::from_utf8_lossy(&out.stderr).to_string()),
};

let _ = io::stdout().write(&out.stdout);
let _ = io::stdout().flush();
let _ = io::stderr().write(&out.stderr);
let _ = io::stderr().flush();

let te = TelemetryEvent::RustcRun { duration_ms: ms,
exit_code: exit_code,
errors: errors };
telemetry::log_telemetry(te, cfg);
process::exit(exit_code);
},
Err(e) => {
let exit_code = e.raw_os_error().unwrap_or(1);
let te = TelemetryEvent::RustcRun { duration_ms: ms,
exit_code: exit_code,
errors: Some(format!("{}", e)) };
telemetry::log_telemetry(te, cfg);
Err(multirust_utils::Error::RunningCommand {
name: args[0].as_ref().to_owned(),
error: multirust_utils::raw::CommandError::Io(e),
}.into())
},
}
}

fn run_command_for_dir_without_telemetry<S: AsRef<OsStr>>(cmd: Command, args: &[S]) -> Result<()> {
let output = bare_run_command_for_dir(cmd, &args);

match output {
Ok(out) => {
let _ = io::stdout().write(&out.stdout);
let _ = io::stdout().flush();
let _ = io::stderr().write(&out.stderr);
let _ = io::stderr().flush();

let status = out.status;
// Ensure correct exit code is returned
let code = status.code().unwrap_or(1);
process::exit(code);
}
Err(e) => {
Err(multirust_utils::Error::RunningCommand {
name: args[0].as_ref().to_owned(),
error: multirust_utils::raw::CommandError::Io(e),
}.into())
}
}
}

fn bare_run_command_for_dir<S: AsRef<OsStr>>(mut cmd: Command, args: &[S]) -> std::result::Result<Output, std::io::Error> {
cmd.args(&args[1..]);

// FIXME rust-lang/rust#32254. It's not clear to me
// when and why this is needed.
cmd.stdin(process::Stdio::inherit());

cmd.output()
}



34 changes: 34 additions & 0 deletions src/multirust/telemetry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use time;
use config::Cfg;
use multirust_utils::utils;
use rustc_serialize::json;

#[derive(Debug, PartialEq)]
pub enum TelemetryMode {
On,
Off,
}

#[derive(RustcDecodable, RustcEncodable, Debug)]
pub enum TelemetryEvent {
RustcRun { duration_ms: u64, exit_code: i32, errors: Option<String> },
ToolchainUpdate { toolchain: String, success: bool } ,
TargetAdd { toolchain: String, target: String, success: bool },
}

#[derive(RustcDecodable, RustcEncodable, Debug)]
struct LogMessage {
log_time_s: i64,
event: TelemetryEvent,
}

pub fn log_telemetry(event: TelemetryEvent, cfg: &Cfg) {
let ln = LogMessage { log_time_s: time::get_time().sec, event: event };

let json = json::encode(&ln).unwrap();

let now = time::now_utc();
let filename = format!("telemetry-{}-{:02}-{:02}", now.tm_year + 1900, now.tm_mon, now.tm_mday);

let _ = utils::append_file("telemetry", &cfg.multirust_dir.join(&filename), &json);
}
8 changes: 4 additions & 4 deletions src/rustup-cli/multirust_mode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clap::ArgMatches;
use cli;
use common::{self, confirm, set_globals, run_inner,
use common::{self, confirm, set_globals,
show_channel_update, show_tool_versions,
update_all_channels};
use rustup::*;
Expand Down Expand Up @@ -79,14 +79,14 @@ fn run(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
let args = m.values_of("command").unwrap();
let args: Vec<_> = args.collect();
let cmd = try!(toolchain.create_command(args[0]));
run_inner(cmd, &args)
command::run_command_for_dir(cmd, &args, &cfg)
}

fn proxy(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
let args = m.values_of("command").unwrap();
let args: Vec<_> = args.collect();
let cmd = try!(cfg.create_command_for_dir(&try!(utils::current_dir()), args[0]));
run_inner(cmd, &args)
command::run_command_for_dir(cmd, &args, &cfg)
}

fn command_requires_metadata() -> Result<bool> {
Expand Down Expand Up @@ -326,7 +326,7 @@ fn add_target(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
pkg: "rust-std".to_string(),
target: TargetTriple::from_str(target),
};
try!(toolchain.add_component(new_component));
try!(toolchain.add_component(new_component, &cfg));

Ok(())
}
Expand Down
4 changes: 3 additions & 1 deletion src/rustup-cli/proxy_mode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use common::{run_inner, set_globals};
use rustup::{Cfg, Result, Error};
use rustup_utils::utils;
use rustup::command::run_command_for_dir;
use std::env;
use std::path::PathBuf;
use job;
Expand All @@ -26,6 +27,7 @@ pub fn main() -> Result<()> {
fn direct_proxy(cfg: &Cfg, arg0: &str) -> Result<()> {
let cmd = try!(cfg.create_command_for_dir(&try!(utils::current_dir()), arg0));
let args: Vec<_> = env::args_os().collect();
run_inner(cmd, &args)

run_command_for_dir(cmd, &args, &cfg)
}

1 change: 0 additions & 1 deletion src/rustup-utils/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use winreg;
pub use raw::{is_directory, is_file, path_exists, if_not_empty, random_string, prefix_arg,
has_cmd, find_cmd};


pub fn ensure_dir_exists(name: &'static str,
path: &Path,
notify_handler: NotifyHandler)
Expand Down
52 changes: 52 additions & 0 deletions src/rustup/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::env;
use std::io;
use std::process::Command;
use std::fmt::{self, Display};
use std::str::FromStr;

use itertools::Itertools;

Expand All @@ -12,6 +13,7 @@ use rustup_dist::{temp, dist};
use rustup_utils::utils;
use override_db::OverrideDB;
use toolchain::{Toolchain, UpdateStatus};
use telemetry::{TelemetryMode};

// Note: multirust-rs jumped from 2 to 12 to leave multirust.sh room to diverge
pub const METADATA_VERSION: &'static str = "12";
Expand All @@ -22,6 +24,8 @@ pub enum OverrideReason {
OverrideDB(PathBuf),
}



impl Display for OverrideReason {
fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
match *self {
Expand All @@ -46,6 +50,7 @@ pub struct Cfg {
pub env_override: Option<String>,
pub dist_root_url: Cow<'static, str>,
pub notify_handler: SharedNotifyHandler,
pub telemetry_mode: TelemetryMode,
}

impl Cfg {
Expand Down Expand Up @@ -86,6 +91,8 @@ impl Cfg {
.and_then(utils::if_not_empty)
.map_or(Cow::Borrowed(dist::DEFAULT_DIST_ROOT), Cow::Owned);

let telemetry_mode = Cfg::find_telemetry(&multirust_dir.join("telemetry"));

Ok(Cfg {
multirust_dir: multirust_dir,
version_file: version_file,
Expand All @@ -97,6 +104,7 @@ impl Cfg {
gpg_key: gpg_key,
notify_handler: notify_handler,
env_override: env_override,
telemetry_mode: telemetry_mode,
dist_root_url: dist_root_url,
})
}
Expand Down Expand Up @@ -402,4 +410,48 @@ impl Cfg {
Ok(name.to_owned())
}
}

pub fn set_telemetry(&self, telemetry: &str) -> Result<()> {
let work_file = try!(self.temp_cfg.new_file());

try!(utils::write_file("temp", &work_file, telemetry));

try!(utils::rename_file("telemetry", &*work_file, &self.multirust_dir.join("telemetry")));

self.notify_handler.call(Notification::SetTelemetry(telemetry));

Ok(())
}

pub fn telemetry_enabled(&self) -> bool {
match self.telemetry_mode {
TelemetryMode::On => true,
TelemetryMode::Off => false,
}
}

fn find_telemetry(telemetry_file: &PathBuf) -> TelemetryMode {
// default telemetry should be off - if no telemetry file is found, it's off
if !utils::is_file(telemetry_file) {
return TelemetryMode::Off;
}

let content = utils::read_file("telemetry", telemetry_file);
let telemetry_string = content.unwrap_or("off".to_string());

// If the telemetry file is empty, for some reason, assume that the user
// would prefer no telemetry and set telemetry to off.
if telemetry_string.is_empty() {
return TelemetryMode::Off;
}

// Finally, match any remaining string - if this is anything but 'on',
// assume that the user would prefer no telemetry be collected and set
// telemetry to off.
if !telemetry_string.starts_with("on") {
return TelemetryMode::Off;
}

TelemetryMode::On
}
}
5 changes: 4 additions & 1 deletion src/rustup/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum Notification<'a> {
NonFatalError(&'a Error),
UpgradeRemovesToolchains,
MissingFileDuringSelfUninstall(PathBuf),
SetTelemetry(&'a str),
}

#[derive(Debug)]
Expand Down Expand Up @@ -101,7 +102,8 @@ impl<'a> Notification<'a> {
UninstalledToolchain(_) |
ToolchainNotInstalled(_) |
UpgradingMetadata(_, _) |
MetadataUpgradeNotNeeded(_) => NotificationLevel::Info,
MetadataUpgradeNotNeeded(_) |
SetTelemetry(_) => NotificationLevel::Info,
NonFatalError(_) => NotificationLevel::Error,
UpgradeRemovesToolchains |
MissingFileDuringSelfUninstall(_) => NotificationLevel::Warn,
Expand Down Expand Up @@ -153,6 +155,7 @@ impl<'a> Display for Notification<'a> {
MissingFileDuringSelfUninstall(ref p) => {
write!(f, "expected file does not exist to uninstall: {}", p.display())
}
SetTelemetry(telemetry_status) => write!(f, "telemetry set to '{}'", telemetry_status)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/rustup/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ extern crate rustup_utils;
extern crate hyper;
extern crate regex;
extern crate itertools;
extern crate rustc_serialize;
extern crate time;

pub use errors::*;
pub use config::*;
Expand All @@ -17,3 +19,5 @@ mod toolchain;
mod config;
mod env_var;
mod install;
pub mod telemetry;
pub mod command;
Loading

0 comments on commit a5128e8

Please sign in to comment.