blob: c75639801c9574a89a3361f48649c7fa0b44e2a8 [file] [log] [blame]
// Copyright 2013 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: jefftk@google.com (Jeff Kaufman)
#include "pagespeed/system/system_server_context.h"
#include "base/logging.h"
#include "net/instaweb/http/public/url_async_fetcher.h"
#include "net/instaweb/http/public/url_async_fetcher_stats.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_driver_factory.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_stats.h"
#include "pagespeed/system/add_headers_fetcher.h"
#include "pagespeed/system/loopback_route_fetcher.h"
#include "pagespeed/system/system_cache_path.h"
#include "pagespeed/system/system_caches.h"
#include "pagespeed/system/system_request_context.h"
#include "pagespeed/system/system_rewrite_driver_factory.h"
#include "pagespeed/system/system_rewrite_options.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/file_system.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/null_message_handler.h"
#include "pagespeed/kernel/base/split_statistics.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/sharedmem/shared_mem_statistics.h"
namespace net_instaweb {
class PurgeSet;
namespace {
const char kHtmlRewriteTimeUsHistogram[] = "Html Time us Histogram";
const char kLocalFetcherStatsPrefix[] = "http";
const char kCacheFlushCount[] = "cache_flush_count";
const char kCacheFlushTimestampMs[] = "cache_flush_timestamp_ms";
const char kStatistics404Count[] = "statistics_404_count";
} // namespace
SystemServerContext::SystemServerContext(
RewriteDriverFactory* factory, StringPiece hostname, int port)
: ServerContext(factory),
initialized_(false),
use_per_vhost_statistics_(false),
cache_flush_mutex_(thread_system()->NewMutex()),
last_cache_flush_check_sec_(0),
cache_flush_count_(NULL), // Lazy-initialized under mutex.
cache_flush_timestamp_ms_(NULL), // Lazy-initialized under mutex.
html_rewrite_time_us_histogram_(NULL),
local_statistics_(NULL),
hostname_identifier_(StrCat(hostname, ":", IntegerToString(port))),
system_caches_(NULL),
cache_path_(NULL) {
global_system_rewrite_options()->set_description(hostname_identifier_);
}
SystemServerContext::~SystemServerContext() {
if (cache_path_ != NULL) {
cache_path_->RemoveServerContext(this);
}
}
void SystemServerContext::SetCachePath(SystemCachePath* cache_path) {
DCHECK(cache_path_ == NULL);
cache_path_ = cache_path;
cache_path->AddServerContext(this);
}
// If we haven't checked the timestamp of $FILE_PREFIX/cache.flush in the past
// cache_flush_poll_interval_sec_ seconds do so, and if the timestamp has
// expired then update the cache_invalidation_timestamp in global_options,
// thus flushing the cache.
void SystemServerContext::FlushCacheIfNecessary() {
if (global_system_rewrite_options()->enable_cache_purge()) {
cache_path_->FlushCacheIfNecessary();
} else {
CheckLegacyGlobalCacheFlushFile();
}
}
void SystemServerContext::CheckLegacyGlobalCacheFlushFile() {
int64 cache_flush_poll_interval_sec =
global_system_rewrite_options()->cache_flush_poll_interval_sec();
if (cache_flush_poll_interval_sec > 0) {
int64 now_sec = timer()->NowMs() / Timer::kSecondMs;
bool check_cache_file = false;
{
ScopedMutex lock(cache_flush_mutex_.get());
if (now_sec >= (last_cache_flush_check_sec_ +
cache_flush_poll_interval_sec)) {
last_cache_flush_check_sec_ = now_sec;
check_cache_file = true;
}
if (cache_flush_count_ == NULL) {
cache_flush_count_ = statistics()->GetVariable(kCacheFlushCount);
}
if (cache_flush_timestamp_ms_ == NULL) {
cache_flush_timestamp_ms_ = statistics()->GetUpDownCounter(
kCacheFlushTimestampMs);
}
}
if (check_cache_file) {
GoogleString cache_flush_filename =
global_system_rewrite_options()->cache_flush_filename();
if (cache_flush_filename.empty()) {
cache_flush_filename = "cache.flush";
}
if (cache_flush_filename[0] != '/') {
// Implementations must ensure the file cache path is an absolute path.
// mod_pagespeed checks in mod_instaweb.cc:pagespeed_post_config while
// ngx_pagespeed checks in ngx_pagespeed.cc:ps_merge_srv_conf.
DCHECK_EQ('/', global_system_rewrite_options()->file_cache_path()[0]);
cache_flush_filename = StrCat(
global_system_rewrite_options()->file_cache_path(), "/",
cache_flush_filename);
}
int64 cache_flush_timestamp_sec;
NullMessageHandler null_handler;
if (file_system()->Mtime(cache_flush_filename,
&cache_flush_timestamp_sec,
&null_handler)) {
int64 timestamp_ms = cache_flush_timestamp_sec * Timer::kSecondMs;
bool flushed = UpdateCacheFlushTimestampMs(timestamp_ms);
// The multiple child processes each must independently
// discover a fresh cache.flush and update the options. However,
// as shown in
// http://code.google.com/p/modpagespeed/issues/detail?id=568
// we should only bump the flush-count and print a warning to
// the log once per new timestamp.
if (flushed &&
(timestamp_ms !=
cache_flush_timestamp_ms_->SetReturningPreviousValue(
timestamp_ms))) {
int count = cache_flush_count_->Add(1);
message_handler()->Message(kWarning, "Cache Flush %d", count);
}
}
} else {
// Check on every request whether another child process has updated the
// statistic.
int64 timestamp_ms = cache_flush_timestamp_ms_->Get();
// Do the difference-check first because that involves only a
// reader-lock, so we have zero contention risk when the cache is not
// being flushed.
if ((timestamp_ms > 0) &&
global_options()->has_cache_invalidation_timestamp_ms() &&
(global_options()->cache_invalidation_timestamp() < timestamp_ms)) {
UpdateCacheFlushTimestampMs(timestamp_ms);
}
}
}
}
void SystemServerContext::UpdateCachePurgeSet(
const CopyOnWrite<PurgeSet>& purge_set) {
global_options()->UpdateCachePurgeSet(purge_set);
if (cache_flush_count_ == NULL) {
cache_flush_count_ = statistics()->GetVariable(kCacheFlushCount);
}
cache_flush_count_->Add(1);
}
bool SystemServerContext::UpdateCacheFlushTimestampMs(int64 timestamp_ms) {
return global_options()->UpdateCacheInvalidationTimestampMs(timestamp_ms);
}
void SystemServerContext::AddHtmlRewriteTimeUs(int64 rewrite_time_us) {
if (html_rewrite_time_us_histogram_ != NULL) {
html_rewrite_time_us_histogram_->Add(rewrite_time_us);
}
}
SystemRewriteOptions* SystemServerContext::global_system_rewrite_options() {
SystemRewriteOptions* out =
dynamic_cast<SystemRewriteOptions*>(global_options());
CHECK(out != NULL);
return out;
}
void SystemServerContext::PostInitHook() {
ServerContext::PostInitHook();
admin_site_.reset(new AdminSite(static_asset_manager(), timer(),
message_handler()));
}
void SystemServerContext::CreateLocalStatistics(
Statistics* global_statistics,
SystemRewriteDriverFactory* factory) {
local_statistics_ =
factory->AllocateAndInitSharedMemStatistics(
true /* local */, hostname_identifier(),
*global_system_rewrite_options());
split_statistics_.reset(new SplitStatistics(
factory->thread_system(), local_statistics_, global_statistics));
// local_statistics_ was ::InitStat'd by AllocateAndInitSharedMemStatistics,
// but we need to take care of split_statistics_.
factory->NonStaticInitStats(split_statistics_.get());
}
void SystemServerContext::InitStats(Statistics* statistics) {
statistics->AddVariable(kCacheFlushCount);
statistics->AddUpDownCounter(kCacheFlushTimestampMs);
statistics->AddVariable(kStatistics404Count);
Histogram* html_rewrite_time_us_histogram =
statistics->AddHistogram(kHtmlRewriteTimeUsHistogram);
// We set the boundary at 2 seconds which is about 2 orders of magnitude worse
// than anything we have reasonably seen, to make sure we don't cut off actual
// samples.
html_rewrite_time_us_histogram->SetMaxValue(2 * Timer::kSecondUs);
UrlAsyncFetcherStats::InitStats(kLocalFetcherStatsPrefix, statistics);
}
Variable* SystemServerContext::statistics_404_count() {
return statistics()->GetVariable(kStatistics404Count);
}
void SystemServerContext::ChildInit(SystemRewriteDriverFactory* factory) {
DCHECK(!initialized_);
use_per_vhost_statistics_ = factory->use_per_vhost_statistics();
if (!initialized_ && !global_options()->unplugged()) {
initialized_ = true;
system_caches_ = factory->caches();
set_lock_manager(factory->caches()->GetLockManager(
global_system_rewrite_options()));
UrlAsyncFetcher* fetcher =
factory->GetFetcher(global_system_rewrite_options());
set_default_system_fetcher(fetcher);
if (split_statistics_.get() != NULL) {
// Readjust the SHM stuff for the new process
local_statistics_->Init(false, message_handler());
// Create local stats for the ServerContext, and fill in its
// statistics() and rewrite_stats() using them; if we didn't do this here
// they would get set to the factory's by the InitServerContext call
// below.
set_statistics(split_statistics_.get());
local_rewrite_stats_.reset(new RewriteStats(
split_statistics_.get(), factory->thread_system(), factory->timer()));
set_rewrite_stats(local_rewrite_stats_.get());
// In case of gzip fetching, we will have the UrlAsyncFetcherStats take
// care of decompression rather than the original fetcher, so we get
// correct numbers for bytes fetched.
bool fetch_with_gzip = global_system_rewrite_options()->fetch_with_gzip();
if (fetch_with_gzip) {
fetcher->set_fetch_with_gzip(false);
}
stats_fetcher_.reset(new UrlAsyncFetcherStats(
kLocalFetcherStatsPrefix, fetcher,
factory->timer(), split_statistics_.get()));
if (fetch_with_gzip) {
stats_fetcher_->set_fetch_with_gzip(true);
}
set_default_system_fetcher(stats_fetcher_.get());
}
// To allow Flush to come in while multiple threads might be
// referencing the signature, we must be able to mutate the
// timestamp and signature atomically. RewriteOptions supports
// an optional read/writer lock for this purpose.
global_options()->set_cache_invalidation_timestamp_mutex(
thread_system()->NewRWLock());
factory->InitServerContext(this);
html_rewrite_time_us_histogram_ = statistics()->GetHistogram(
kHtmlRewriteTimeUsHistogram);
html_rewrite_time_us_histogram_->SetMaxValue(2 * Timer::kSecondUs);
}
}
void SystemServerContext::ApplySessionFetchers(
const RequestContextPtr& request, RewriteDriver* driver) {
const SystemRewriteOptions* conf =
SystemRewriteOptions::DynamicCast(driver->options());
CHECK(conf != NULL);
SystemRequestContext* system_request = SystemRequestContext::DynamicCast(
request.get());
if (system_request == NULL) {
return; // decoding_driver has a null RequestContext.
}
// Note that these fetchers are applied in the opposite order of how they are
// added: the last one added here is the first one applied and vice versa.
//
// Currently, we want AddHeadersFetcher running first, then perhaps
// SpdyFetcher and then LoopbackRouteFetcher (and then Serf).
//
// We want AddHeadersFetcher to run before the SpdyFetcher since we
// want any headers it adds to be visible.
//
// We want SpdyFetcher to run before LoopbackRouteFetcher as it needs
// to know the request hostname, which LoopbackRouteFetcher could potentially
// rewrite to 127.0.0.1; and it's OK without the rewriting since it will
// always talk to the local machine anyway.
SystemRewriteOptions* options = global_system_rewrite_options();
if (!options->disable_loopback_routing() &&
!options->slurping_enabled() &&
!options->test_proxy()) {
// Note the port here is our port, not from the request, since
// LoopbackRouteFetcher may decide we should be talking to ourselves.
driver->SetSessionFetcher(new LoopbackRouteFetcher(
driver->options(), system_request->local_ip(),
system_request->local_port(), driver->async_fetcher()));
}
// Apache has experimental support for direct fetching from mod_spdy. Other
// implementations that support something similar would use this hook.
MaybeApplySpdySessionFetcher(request, driver);
if (driver->options()->num_custom_fetch_headers() > 0) {
driver->SetSessionFetcher(new AddHeadersFetcher(driver->options(),
driver->async_fetcher()));
}
}
void SystemServerContext::CollapseConfigOverlaysAndComputeSignatures() {
ComputeSignature(global_system_rewrite_options());
}
const SystemRewriteOptions* SystemServerContext::SpdyGlobalConfig() const {
// Subclasses can override to point to the SPDY configuration.
// ../apache/apache_server_context.h does.
return NULL;
}
// Handler which serves PSOL console.
void SystemServerContext::ConsoleHandler(
const SystemRewriteOptions& options,
AdminSite::AdminSource source,
const QueryParams& query_params, AsyncFetch* fetch) {
admin_site_->ConsoleHandler(*global_system_rewrite_options(), options,
source, query_params, fetch, statistics());
}
void SystemServerContext::StatisticsHandler(
const RewriteOptions& options,
bool is_global_request,
AdminSite::AdminSource source,
AsyncFetch* fetch) {
if (!use_per_vhost_statistics_) {
is_global_request = true;
}
Statistics* stats = is_global_request ? factory()->statistics()
: statistics();
admin_site_->StatisticsHandler(options, source, fetch, stats);
}
void SystemServerContext::ConsoleJsonHandler(
const QueryParams& params, AsyncFetch* fetch) {
admin_site_->ConsoleJsonHandler(params, fetch, statistics());
}
void SystemServerContext::PrintHistograms(
bool is_global_request,
AdminSite::AdminSource source,
AsyncFetch* fetch) {
Statistics* stats = is_global_request ? factory()->statistics()
: statistics();
admin_site_->PrintHistograms(source, fetch, stats);
}
void SystemServerContext::PrintCaches(bool is_global,
AdminSite::AdminSource source,
const GoogleUrl& stripped_gurl,
const QueryParams& query_params,
const RewriteOptions* options,
AsyncFetch* fetch) {
admin_site_->PrintCaches(is_global, source, stripped_gurl, query_params,
options, cache_path(), fetch, system_caches_,
filesystem_metadata_cache(), http_cache(),
metadata_cache(), page_property_cache(), this);
}
void SystemServerContext::PrintNormalConfig(
AdminSite::AdminSource source, AsyncFetch* fetch) {
admin_site_->PrintNormalConfig(source, fetch,
global_system_rewrite_options());
}
void SystemServerContext::PrintSpdyConfig(
AdminSite::AdminSource source, AsyncFetch* fetch) {
const SystemRewriteOptions* spdy_config = SpdyGlobalConfig();
admin_site_->PrintSpdyConfig(source, fetch, spdy_config);
}
void SystemServerContext::MessageHistoryHandler(
const RewriteOptions& options, AdminSite::AdminSource source,
AsyncFetch* fetch) {
admin_site_->MessageHistoryHandler(options, source, fetch);
}
void SystemServerContext::AdminPage(
bool is_global, const GoogleUrl& stripped_gurl,
const QueryParams& query_params,
const RewriteOptions* options,
AsyncFetch* fetch) {
const SystemRewriteOptions* spdy_config = SpdyGlobalConfig();
Statistics* stats = is_global ? factory()->statistics()
: statistics();
admin_site_->AdminPage(is_global, stripped_gurl, query_params, options,
cache_path(), fetch, system_caches_,
filesystem_metadata_cache(), http_cache(),
metadata_cache(), page_property_cache(), this,
statistics(), stats, global_system_rewrite_options(),
spdy_config);
}
void SystemServerContext::StatisticsPage(bool is_global,
const QueryParams& query_params,
const RewriteOptions* options,
AsyncFetch* fetch) {
const SystemRewriteOptions* spdy_config = SpdyGlobalConfig();
Statistics* stats = is_global ? factory()->statistics()
: statistics();
admin_site_->StatisticsPage(
is_global, query_params, options, fetch,
system_caches_, filesystem_metadata_cache(), http_cache(),
metadata_cache(), page_property_cache(), this, statistics(), stats,
global_system_rewrite_options(), spdy_config);
}
} // namespace net_instaweb