blob: 92740d9e2b1d34e719717df9e9925b95016270c8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ppapi/native_client/src/trusted/plugin/pnacl_coordinator.h"
#include <utility>
#include <vector>
#include "native_client/src/include/checked_cast.h"
#include "native_client/src/include/portability_io.h"
#include "native_client/src/shared/platform/nacl_check.h"
#include "native_client/src/trusted/service_runtime/include/sys/stat.h"
#include "ppapi/c/pp_bool.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/private/ppb_uma_private.h"
#include "ppapi/native_client/src/trusted/plugin/plugin.h"
#include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
#include "ppapi/native_client/src/trusted/plugin/pnacl_translate_thread.h"
#include "ppapi/native_client/src/trusted/plugin/service_runtime.h"
#include "ppapi/native_client/src/trusted/plugin/temporary_file.h"
namespace plugin {
//////////////////////////////////////////////////////////////////////
// UMA stat helpers.
//////////////////////////////////////////////////////////////////////
namespace {
// Assume translation time metrics *can be* large.
// Up to 12 minutes.
const int64_t kTimeLargeMin = 10; // in ms
const int64_t kTimeLargeMax = 720000; // in ms
const uint32_t kTimeLargeBuckets = 100;
const int32_t kSizeKBMin = 1;
const int32_t kSizeKBMax = 512*1024; // very large .pexe / .nexe.
const uint32_t kSizeKBBuckets = 100;
const int32_t kRatioMin = 10;
const int32_t kRatioMax = 10*100; // max of 10x difference.
const uint32_t kRatioBuckets = 100;
const int32_t kKBPSMin = 1;
const int32_t kKBPSMax = 30*1000; // max of 30 MB / sec.
const uint32_t kKBPSBuckets = 100;
void HistogramTime(pp::UMAPrivate& uma,
const nacl::string& name, int64_t ms) {
if (ms < 0) return;
uma.HistogramCustomTimes(name,
ms,
kTimeLargeMin, kTimeLargeMax,
kTimeLargeBuckets);
}
void HistogramSizeKB(pp::UMAPrivate& uma,
const nacl::string& name, int32_t kb) {
if (kb < 0) return;
uma.HistogramCustomCounts(name,
kb,
kSizeKBMin, kSizeKBMax,
kSizeKBBuckets);
}
void HistogramRatio(pp::UMAPrivate& uma,
const nacl::string& name, int64_t a, int64_t b) {
if (a < 0 || b <= 0) return;
uma.HistogramCustomCounts(name,
100 * a / b,
kRatioMin, kRatioMax,
kRatioBuckets);
}
void HistogramKBPerSec(pp::UMAPrivate& uma,
const nacl::string& name, double kb, double s) {
if (kb < 0.0 || s <= 0.0) return;
uma.HistogramCustomCounts(name,
static_cast<int64_t>(kb / s),
kKBPSMin, kKBPSMax,
kKBPSBuckets);
}
void HistogramEnumerateTranslationCache(pp::UMAPrivate& uma, bool hit) {
uma.HistogramEnumeration("NaCl.Perf.PNaClCache.IsHit",
hit, 2);
}
// Opt level is expected to be 0 to 3. Treating 4 as unknown.
const int8_t kOptUnknown = 4;
void HistogramOptLevel(pp::UMAPrivate& uma, int8_t opt_level) {
if (opt_level < 0 || opt_level > 3) {
opt_level = kOptUnknown;
}
uma.HistogramEnumeration("NaCl.Options.PNaCl.OptLevel",
opt_level, kOptUnknown+1);
}
nacl::string GetArchitectureAttributes(Plugin* plugin) {
pp::Var attrs_var(pp::PASS_REF,
plugin->nacl_interface()->GetCpuFeatureAttrs());
return attrs_var.AsString();
}
} // namespace
//////////////////////////////////////////////////////////////////////
// The coordinator class.
//////////////////////////////////////////////////////////////////////
// Out-of-line destructor to keep it from getting put in every .o where
// callback_source.h is included
template<>
CallbackSource<FileStreamData>::~CallbackSource() {}
PnaclCoordinator* PnaclCoordinator::BitcodeToNative(
Plugin* plugin,
const nacl::string& pexe_url,
const PP_PNaClOptions& pnacl_options,
const pp::CompletionCallback& translate_notify_callback) {
PLUGIN_PRINTF(("PnaclCoordinator::BitcodeToNative (plugin=%p, pexe=%s)\n",
static_cast<void*>(plugin), pexe_url.c_str()));
PnaclCoordinator* coordinator =
new PnaclCoordinator(plugin, pexe_url,
pnacl_options,
translate_notify_callback);
coordinator->pnacl_init_time_ = NaClGetTimeOfDayMicroseconds();
int cpus = plugin->nacl_interface()->GetNumberOfProcessors();
coordinator->split_module_count_ = std::min(4, std::max(1, cpus));
// First start a network request for the pexe, to tickle the component
// updater's On-Demand resource throttler, and to get Last-Modified/ETag
// cache information. We can cancel the request later if there's
// a bitcode->nexe cache hit.
coordinator->OpenBitcodeStream();
return coordinator;
}
PnaclCoordinator::PnaclCoordinator(
Plugin* plugin,
const nacl::string& pexe_url,
const PP_PNaClOptions& pnacl_options,
const pp::CompletionCallback& translate_notify_callback)
: translate_finish_error_(PP_OK),
plugin_(plugin),
translate_notify_callback_(translate_notify_callback),
translation_finished_reported_(false),
pexe_url_(pexe_url),
pnacl_options_(pnacl_options),
architecture_attributes_(GetArchitectureAttributes(plugin)),
split_module_count_(1),
num_object_files_opened_(0),
is_cache_hit_(PP_FALSE),
error_already_reported_(false),
pnacl_init_time_(0),
pexe_size_(0),
pexe_bytes_compiled_(0),
expected_pexe_size_(-1) {
PLUGIN_PRINTF(("PnaclCoordinator::PnaclCoordinator (this=%p, plugin=%p)\n",
static_cast<void*>(this), static_cast<void*>(plugin)));
callback_factory_.Initialize(this);
}
PnaclCoordinator::~PnaclCoordinator() {
PLUGIN_PRINTF(("PnaclCoordinator::~PnaclCoordinator (this=%p, "
"translate_thread=%p\n",
static_cast<void*>(this), translate_thread_.get()));
// Stopping the translate thread will cause the translate thread to try to
// run translation_complete_callback_ on the main thread. This destructor is
// running from the main thread, and by the time it exits, callback_factory_
// will have been destroyed. This will result in the cancellation of
// translation_complete_callback_, so no notification will be delivered.
if (translate_thread_.get() != NULL) {
translate_thread_->AbortSubprocesses();
}
if (!translation_finished_reported_) {
plugin_->nacl_interface()->ReportTranslationFinished(
plugin_->pp_instance(),
PP_FALSE);
}
// Force deleting the translate_thread now. It must be deleted
// before any scoped_* fields hanging off of PnaclCoordinator
// since the thread may be accessing those fields.
// It will also be accessing obj_files_.
translate_thread_.reset(NULL);
// TODO(jvoung): use base/memory/scoped_vector.h to hold obj_files_.
for (int i = 0; i < num_object_files_opened_; i++) {
delete obj_files_[i];
}
}
PP_FileHandle PnaclCoordinator::TakeTranslatedFileHandle() {
DCHECK(temp_nexe_file_ != NULL);
return temp_nexe_file_->TakeFileHandle();
}
void PnaclCoordinator::ReportNonPpapiError(PP_NaClError err_code,
const nacl::string& message) {
ErrorInfo error_info;
error_info.SetReport(err_code, message);
plugin_->ReportLoadError(error_info);
ExitWithError();
}
void PnaclCoordinator::ReportPpapiError(PP_NaClError err_code,
int32_t pp_error,
const nacl::string& message) {
nacl::stringstream ss;
ss << "PnaclCoordinator: " << message << " (pp_error=" << pp_error << ").";
ErrorInfo error_info;
error_info.SetReport(err_code, ss.str());
plugin_->ReportLoadError(error_info);
ExitWithError();
}
void PnaclCoordinator::ExitWithError() {
PLUGIN_PRINTF(("PnaclCoordinator::ExitWithError\n"));
// Free all the intermediate callbacks we ever created.
// Note: this doesn't *cancel* the callbacks from the factories attached
// to the various helper classes (e.g., pnacl_resources). Thus, those
// callbacks may still run asynchronously. We let those run but ignore
// any other errors they may generate so that they do not end up running
// translate_notify_callback_, which has already been freed.
callback_factory_.CancelAll();
if (!error_already_reported_) {
error_already_reported_ = true;
translation_finished_reported_ = true;
plugin_->nacl_interface()->ReportTranslationFinished(
plugin_->pp_instance(),
PP_FALSE);
translate_notify_callback_.Run(PP_ERROR_FAILED);
} else {
PLUGIN_PRINTF(("PnaclCoordinator::ExitWithError an earlier error was "
"already reported -- Skipping.\n"));
}
}
// Signal that Pnacl translation completed normally.
void PnaclCoordinator::TranslateFinished(int32_t pp_error) {
PLUGIN_PRINTF(("PnaclCoordinator::TranslateFinished (pp_error=%"
NACL_PRId32 ")\n", pp_error));
// Bail out if there was an earlier error (e.g., pexe load failure),
// or if there is an error from the translation thread.
if (translate_finish_error_ != PP_OK || pp_error != PP_OK) {
plugin_->ReportLoadError(error_info_);
ExitWithError();
return;
}
// Send out one last progress event, to finish up the progress events
// that were delayed (see the delay inserted in BitcodeGotCompiled).
if (ExpectedProgressKnown()) {
pexe_bytes_compiled_ = expected_pexe_size_;
plugin_->EnqueueProgressEvent(PP_NACL_EVENT_PROGRESS,
pexe_url_,
plugin::Plugin::LENGTH_IS_COMPUTABLE,
pexe_bytes_compiled_,
expected_pexe_size_);
}
// If there are no errors, report stats from this thread (the main thread).
HistogramOptLevel(plugin_->uma_interface(), pnacl_options_.opt_level);
HistogramKBPerSec(plugin_->uma_interface(),
"NaCl.Perf.PNaClLoadTime.CompileKBPerSec",
pexe_size_ / 1024.0,
translate_thread_->GetCompileTime() / 1000000.0);
HistogramSizeKB(plugin_->uma_interface(), "NaCl.Perf.Size.Pexe",
static_cast<int64_t>(pexe_size_ / 1024));
struct nacl_abi_stat stbuf;
struct NaClDesc* desc = temp_nexe_file_->read_wrapper()->desc();
int stat_ret;
if (0 != (stat_ret = (*((struct NaClDescVtbl const *) desc->base.vtbl)->
Fstat)(desc, &stbuf))) {
PLUGIN_PRINTF(("PnaclCoordinator::TranslateFinished can't stat nexe.\n"));
} else {
size_t nexe_size = stbuf.nacl_abi_st_size;
HistogramSizeKB(plugin_->uma_interface(),
"NaCl.Perf.Size.PNaClTranslatedNexe",
static_cast<int64_t>(nexe_size / 1024));
HistogramRatio(plugin_->uma_interface(),
"NaCl.Perf.Size.PexeNexeSizePct", pexe_size_, nexe_size);
}
int64_t total_time = NaClGetTimeOfDayMicroseconds() - pnacl_init_time_;
HistogramTime(plugin_->uma_interface(),
"NaCl.Perf.PNaClLoadTime.TotalUncachedTime",
total_time / NACL_MICROS_PER_MILLI);
HistogramKBPerSec(plugin_->uma_interface(),
"NaCl.Perf.PNaClLoadTime.TotalUncachedKBPerSec",
pexe_size_ / 1024.0,
total_time / 1000000.0);
// The nexe is written to the temp_nexe_file_. We must Reset() the file
// pointer to be able to read it again from the beginning.
temp_nexe_file_->Reset();
// Report to the browser that translation finished. The browser will take
// care of storing the nexe in the cache.
translation_finished_reported_ = true;
plugin_->nacl_interface()->ReportTranslationFinished(
plugin_->pp_instance(), PP_TRUE);
NexeReadDidOpen(PP_OK);
}
void PnaclCoordinator::NexeReadDidOpen(int32_t pp_error) {
PLUGIN_PRINTF(("PnaclCoordinator::NexeReadDidOpen (pp_error=%"
NACL_PRId32 ")\n", pp_error));
if (pp_error != PP_OK) {
if (pp_error == PP_ERROR_FILENOTFOUND) {
ReportPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_NOTFOUND,
pp_error,
"Failed to open translated nexe (not found).");
return;
}
if (pp_error == PP_ERROR_NOACCESS) {
ReportPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_NOACCESS,
pp_error,
"Failed to open translated nexe (no access).");
return;
}
ReportPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_OTHER,
pp_error,
"Failed to open translated nexe.");
return;
}
translate_notify_callback_.Run(pp_error);
}
void PnaclCoordinator::OpenBitcodeStream() {
// Now open the pexe stream.
streaming_downloader_.reset(new FileDownloader(plugin_));
// Mark the request as requesting a PNaCl bitcode file,
// so that component updater can detect this user action.
streaming_downloader_->set_request_headers(
"Accept: application/x-pnacl, */*");
// Even though we haven't started downloading, create the translation
// thread object immediately. This ensures that any pieces of the file
// that get downloaded before the compilation thread is accepting
// SRPCs won't get dropped.
translate_thread_.reset(new PnaclTranslateThread());
if (translate_thread_ == NULL) {
ReportNonPpapiError(
PP_NACL_ERROR_PNACL_THREAD_CREATE,
"PnaclCoordinator: could not allocate translation thread.");
return;
}
pp::CompletionCallback cb =
callback_factory_.NewCallback(&PnaclCoordinator::BitcodeStreamDidOpen);
if (!streaming_downloader_->OpenStream(pexe_url_, cb, this)) {
ReportNonPpapiError(
PP_NACL_ERROR_PNACL_PEXE_FETCH_OTHER,
nacl::string("PnaclCoordinator: failed to open stream ") + pexe_url_);
return;
}
}
void PnaclCoordinator::BitcodeStreamDidOpen(int32_t pp_error) {
if (pp_error != PP_OK) {
BitcodeStreamDidFinish(pp_error);
// We have not spun up the translation process yet, so we need to call
// TranslateFinished here.
TranslateFinished(pp_error);
return;
}
// The component updater's resource throttles + OnDemand update/install
// should block the URL request until the compiler is present. Now we
// can load the resources (e.g. llc and ld nexes).
resources_.reset(new PnaclResources(plugin_, this));
CHECK(resources_ != NULL);
// The first step of loading resources: read the resource info file.
pp::CompletionCallback resource_info_read_cb =
callback_factory_.NewCallback(&PnaclCoordinator::ResourceInfoWasRead);
resources_->ReadResourceInfo(resource_info_read_cb);
}
void PnaclCoordinator::ResourceInfoWasRead(int32_t pp_error) {
PLUGIN_PRINTF(("PluginCoordinator::ResourceInfoWasRead (pp_error=%"
NACL_PRId32 ")\n", pp_error));
// Second step of loading resources: call StartLoad to load pnacl-llc
// and pnacl-ld, based on the filenames found in the resource info file.
pp::CompletionCallback resources_cb =
callback_factory_.NewCallback(&PnaclCoordinator::ResourcesDidLoad);
resources_->StartLoad(resources_cb);
}
void PnaclCoordinator::ResourcesDidLoad(int32_t pp_error) {
PLUGIN_PRINTF(("PnaclCoordinator::ResourcesDidLoad (pp_error=%"
NACL_PRId32 ")\n", pp_error));
if (pp_error != PP_OK) {
// Finer-grained error code should have already been reported by
// the PnaclResources class.
return;
}
// Okay, now that we've started the HTTP request for the pexe
// and we've ensured that the PNaCl compiler + metadata is installed,
// get the cache key from the response headers and from the
// compiler's version metadata.
nacl::string headers = streaming_downloader_->GetResponseHeaders();
temp_nexe_file_.reset(new TempFile(plugin_));
pp::CompletionCallback cb =
callback_factory_.NewCallback(&PnaclCoordinator::NexeFdDidOpen);
int32_t nexe_fd_err =
plugin_->nacl_interface()->GetNexeFd(
plugin_->pp_instance(),
streaming_downloader_->full_url().c_str(),
// TODO(dschuff): Get this value from the pnacl json file after it
// rolls in from NaCl.
1,
pnacl_options_.opt_level,
headers.c_str(),
architecture_attributes_.c_str(), // Extra compile flags.
&is_cache_hit_,
temp_nexe_file_->internal_handle(),
cb.pp_completion_callback());
if (nexe_fd_err < PP_OK_COMPLETIONPENDING) {
ReportPpapiError(PP_NACL_ERROR_PNACL_CREATE_TEMP, nexe_fd_err,
nacl::string("Call to GetNexeFd failed"));
}
}
void PnaclCoordinator::NexeFdDidOpen(int32_t pp_error) {
PLUGIN_PRINTF(("PnaclCoordinator::NexeFdDidOpen (pp_error=%"
NACL_PRId32 ", hit=%d)\n", pp_error,
is_cache_hit_ == PP_TRUE));
if (pp_error < PP_OK) {
ReportPpapiError(PP_NACL_ERROR_PNACL_CREATE_TEMP, pp_error,
nacl::string("GetNexeFd failed"));
return;
}
if (*temp_nexe_file_->internal_handle() == PP_kInvalidFileHandle) {
ReportNonPpapiError(
PP_NACL_ERROR_PNACL_CREATE_TEMP,
nacl::string(
"PnaclCoordinator: Got bad temp file handle from GetNexeFd"));
return;
}
HistogramEnumerateTranslationCache(plugin_->uma_interface(), is_cache_hit_);
if (is_cache_hit_ == PP_TRUE) {
// Cache hit -- no need to stream the rest of the file.
streaming_downloader_.reset(NULL);
// Open it for reading as the cached nexe file.
NexeReadDidOpen(temp_nexe_file_->Open(false));
} else {
// Open an object file first so the translator can start writing to it
// during streaming translation.
for (int i = 0; i < split_module_count_; i++) {
obj_files_.push_back(new TempFile(plugin_));
int32_t pp_error = obj_files_[i]->Open(true);
if (pp_error != PP_OK) {
ReportPpapiError(PP_NACL_ERROR_PNACL_CREATE_TEMP,
pp_error,
"Failed to open scratch object file.");
} else {
num_object_files_opened_++;
}
}
invalid_desc_wrapper_.reset(plugin_->wrapper_factory()->MakeInvalid());
// Meanwhile, a miss means we know we need to stream the bitcode, so stream
// the rest of it now. (Calling BeginStreaming means that the downloader
// will begin handing data to the coordinator, which is safe any time after
// the translate_thread_ object has been initialized).
pp::CompletionCallback finish_cb = callback_factory_.NewCallback(
&PnaclCoordinator::BitcodeStreamDidFinish);
streaming_downloader_->BeginStreaming(finish_cb);
if (num_object_files_opened_ == split_module_count_) {
// Open the nexe file for connecting ld and sel_ldr.
// Start translation when done with this last step of setup!
RunTranslate(temp_nexe_file_->Open(true));
}
}
}
void PnaclCoordinator::BitcodeStreamDidFinish(int32_t pp_error) {
PLUGIN_PRINTF(("PnaclCoordinator::BitcodeStreamDidFinish (pp_error=%"
NACL_PRId32 ")\n", pp_error));
if (pp_error != PP_OK) {
// Defer reporting the error and cleanup until after the translation
// thread returns, because it may be accessing the coordinator's
// objects or writing to the files.
translate_finish_error_ = pp_error;
if (pp_error == PP_ERROR_ABORTED) {
error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_ABORTED,
"PnaclCoordinator: pexe load failed (aborted).");
}
if (pp_error == PP_ERROR_NOACCESS) {
error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_NOACCESS,
"PnaclCoordinator: pexe load failed (no access).");
} else {
nacl::stringstream ss;
ss << "PnaclCoordinator: pexe load failed (pp_error=" << pp_error << ").";
error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_OTHER, ss.str());
}
translate_thread_->AbortSubprocesses();
} else {
// Compare download completion pct (100% now), to compile completion pct.
HistogramRatio(plugin_->uma_interface(),
"NaCl.Perf.PNaClLoadTime.PctCompiledWhenFullyDownloaded",
pexe_bytes_compiled_, pexe_size_);
}
}
void PnaclCoordinator::BitcodeStreamGotData(int32_t pp_error,
FileStreamData data) {
PLUGIN_PRINTF(("PnaclCoordinator::BitcodeStreamGotData (pp_error=%"
NACL_PRId32 ", data=%p)\n", pp_error, data ? &(*data)[0] : 0));
DCHECK(translate_thread_.get());
translate_thread_->PutBytes(data, pp_error);
// If pp_error > 0, then it represents the number of bytes received.
if (data && pp_error > 0)
pexe_size_ += pp_error;
}
StreamCallback PnaclCoordinator::GetCallback() {
return callback_factory_.NewCallbackWithOutput(
&PnaclCoordinator::BitcodeStreamGotData);
}
void PnaclCoordinator::BitcodeGotCompiled(int32_t pp_error,
int64_t bytes_compiled) {
DCHECK(pp_error == PP_OK);
pexe_bytes_compiled_ += bytes_compiled;
// If we don't know the expected total yet, ask.
if (!ExpectedProgressKnown()) {
int64_t amount_downloaded; // dummy variable.
streaming_downloader_->GetDownloadProgress(&amount_downloaded,
&expected_pexe_size_);
}
// Hold off reporting the last few bytes of progress, since we don't know
// when they are actually completely compiled. "bytes_compiled" only means
// that bytes were sent to the compiler.
if (ExpectedProgressKnown()) {
if (!ShouldDelayProgressEvent()) {
plugin_->EnqueueProgressEvent(PP_NACL_EVENT_PROGRESS,
pexe_url_,
plugin::Plugin::LENGTH_IS_COMPUTABLE,
pexe_bytes_compiled_,
expected_pexe_size_);
}
} else {
plugin_->EnqueueProgressEvent(PP_NACL_EVENT_PROGRESS,
pexe_url_,
plugin::Plugin::LENGTH_IS_NOT_COMPUTABLE,
pexe_bytes_compiled_,
expected_pexe_size_);
}
}
pp::CompletionCallback PnaclCoordinator::GetCompileProgressCallback(
int64_t bytes_compiled) {
return callback_factory_.NewCallback(&PnaclCoordinator::BitcodeGotCompiled,
bytes_compiled);
}
void PnaclCoordinator::DoUMATimeMeasure(int32_t pp_error,
const nacl::string& event_name,
int64_t microsecs) {
DCHECK(pp_error == PP_OK);
HistogramTime(
plugin_->uma_interface(), event_name, microsecs / NACL_MICROS_PER_MILLI);
}
pp::CompletionCallback PnaclCoordinator::GetUMATimeCallback(
const nacl::string& event_name, int64_t microsecs) {
return callback_factory_.NewCallback(&PnaclCoordinator::DoUMATimeMeasure,
event_name,
microsecs);
}
void PnaclCoordinator::GetCurrentProgress(int64_t* bytes_loaded,
int64_t* bytes_total) {
*bytes_loaded = pexe_bytes_compiled_;
*bytes_total = expected_pexe_size_;
}
void PnaclCoordinator::RunTranslate(int32_t pp_error) {
PLUGIN_PRINTF(("PnaclCoordinator::RunTranslate (pp_error=%"
NACL_PRId32 ")\n", pp_error));
// Invoke llc followed by ld off the main thread. This allows use of
// blocking RPCs that would otherwise block the JavaScript main thread.
pp::CompletionCallback report_translate_finished =
callback_factory_.NewCallback(&PnaclCoordinator::TranslateFinished);
CHECK(translate_thread_ != NULL);
translate_thread_->RunTranslate(report_translate_finished,
&obj_files_,
temp_nexe_file_.get(),
invalid_desc_wrapper_.get(),
&error_info_,
resources_.get(),
&pnacl_options_,
architecture_attributes_,
this,
plugin_);
}
} // namespace plugin