blob: 00e29d0f3e543f0dab6c2bdb5b455a40bd8a2376 [file] [log] [blame]
/*
* Copyright 2012 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: jmarantz@google.com (Joshua Marantz)
#include "net/instaweb/rewriter/public/debug_filter.h"
#include "base/logging.h"
#include "net/instaweb/rewriter/public/critical_images_finder.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/html/html_element.h"
#include "pagespeed/kernel/html/html_name.h"
#include "pagespeed/kernel/http/google_url.h"
namespace {
const int64 kTimeNotSet = -1;
} // namespace
namespace net_instaweb {
DebugFilter::Event::Event() {
Clear();
}
void DebugFilter::Event::Clear() {
start_us_ = kTimeNotSet;
duration_us_ = 0;
total_us_ = 0;
}
void DebugFilter::Event::Start(int64 now_us) {
DCHECK_EQ(kTimeNotSet, start_us_);
start_us_ = now_us;
}
void DebugFilter::Event::End(int64 now_us) {
DCHECK_NE(kTimeNotSet, start_us_);
duration_us_ += now_us - start_us_;
start_us_ = kTimeNotSet;
}
void DebugFilter::Event::AddToTotal() {
DCHECK_EQ(kTimeNotSet, start_us_);
total_us_ += duration_us_;
duration_us_ = 0;
}
DebugFilter::DebugFilter(RewriteDriver* driver)
: driver_(driver),
timer_(driver->server_context()->timer()) {
Clear();
driver_->SetDynamicallyDisabledFilterList(&dynamically_disabled_filter_list_);
}
DebugFilter::~DebugFilter() {}
void DebugFilter::Clear() {
num_flushes_ = 0;
end_document_seen_ = false;
idle_.Clear();
parse_.Clear();
render_.Clear();
start_doc_time_us_ = kTimeNotSet;
flush_messages_.clear();
critical_image_urls_.clear();
dynamically_disabled_filter_list_.clear();
}
void DebugFilter::InitParse() {
Clear();
start_doc_time_us_ = timer_->NowUs();
idle_.Start(start_doc_time_us_);
}
void DebugFilter::StartParse() {
int64 now_us = timer_->NowUs();
idle_.End(now_us);
parse_.Start(now_us);
}
void DebugFilter::EndParse() {
int64 now_us = timer_->NowUs();
parse_.End(now_us);
idle_.Start(now_us);
}
void DebugFilter::StartRender() {
int64 now_us = timer_->NowUs();
idle_.End(now_us);
render_.Start(now_us);
}
GoogleString DebugFilter::FormatFlushMessage(int64 time_since_init_parse_us,
int64 parse_duration_us,
int64 render_duration_us,
int64 idle_duration_us) {
// This format is designed for easy searching in View->Page Source.
return StrCat(
"\n"
"#Flush after ", Integer64ToString(time_since_init_parse_us), "us\n"
"#Parse duration ", Integer64ToString(parse_duration_us), "us\n"
"#Render duration ", Integer64ToString(render_duration_us), "us\n",
StrCat(
"#Idle duration ", Integer64ToString(idle_duration_us), "us\n"));
}
GoogleString DebugFilter::FormatEndDocumentMessage(
int64 time_since_init_parse_us, int64 total_parse_duration_us,
int64 total_render_duration_us, int64 total_idle_duration_us,
int num_flushes, bool is_critical_images_beacon_enabled,
const StringSet& critical_image_urls,
const StringVector& dynamically_disabled_filter_list) {
// This format is designed for easy searching in View->Page Source.
GoogleString str = StrCat(
"\n"
"#NumFlushes ",
IntegerToString(num_flushes),
"\n"
"#EndDocument after ",
Integer64ToString(time_since_init_parse_us),
"us\n"
"#Total Parse duration ",
Integer64ToString(total_parse_duration_us), "us\n");
StrAppend(&str, "#Total Render duration ",
Integer64ToString(total_render_duration_us),
"us\n"
"#Total Idle duration ",
Integer64ToString(total_idle_duration_us), "us\n");
if (is_critical_images_beacon_enabled) {
if (critical_image_urls.empty()) {
StrAppend(&str, "No critical images detected.\n");
} else {
StrAppend(&str, "Critical Images:\n\t",
JoinCollection(critical_image_urls, "\n\t"), "\n");
}
}
if (dynamically_disabled_filter_list.empty()) {
StrAppend(&str, "No filters were disabled for this request.\n");
} else {
StrAppend(&str, "The following filters were disabled for this request:\n\t",
JoinCollection(dynamically_disabled_filter_list, "\n\t"), "\n");
}
return str;
}
GoogleString DebugFilter::ListActiveFiltersAndOptions() const {
const RewriteOptions* options = driver_->options();
GoogleString settings_list("\nmod_pagespeed on\nFilters:\n");
StrAppend(&settings_list, options->EnabledFiltersToString());
StrAppend(&settings_list, "\nOptions:\n",
options->SafeEnabledOptionsToString());
return settings_list;
}
void DebugFilter::EndElement(HtmlElement* element) {
if (!flush_messages_.empty()) {
driver_->InsertComment(flush_messages_);
flush_messages_.clear();
}
// Keep track of critical images to print out the list of them at the end. We
// can't just use the critical images stored in the property cache since it
// stores the image hashes, not the full URLs.
if (element->keyword() == HtmlName::kImg) {
// Check if the lazyload filter has modified the original src attribute, and
// if so, use the original src.
HtmlElement::Attribute* src;
if ((src = element->FindAttribute(HtmlName::kDataPagespeedLazySrc)) !=
NULL ||
(src = element->FindAttribute(HtmlName::kSrc)) != NULL) {
GoogleUrl gurl(driver_->base_url(),
StringPiece(src->DecodedValueOrNull()));
GoogleString url_str = gurl.UncheckedSpec().as_string();
CriticalImagesFinder* finder =
driver_->server_context()->critical_images_finder();
if (finder->IsHtmlCriticalImage(url_str, driver_)) {
critical_image_urls_.insert(url_str);
}
}
}
}
void DebugFilter::Flush() {
int64 time_since_init_parse_us = render_.start_us() - start_doc_time_us_;
int64 now_us = timer_->NowUs();
// We get a special StartRender call from RewriteDriver, but we just use
// our Flush event to detect EndRender.
render_.End(now_us);
// Only print a FLUSH message if there is at least one mid-document;
// we don't need to print a FLUSH message at the end of the document
// if there were no other flushes, the summary is sufficient.
if ((num_flushes_ > 0) || !end_document_seen_) {
GoogleString flush_message = FormatFlushMessage(time_since_init_parse_us,
parse_.duration_us(),
render_.duration_us(),
idle_.duration_us());
// If a <style> block spans multiple flushes, calling InsertComment here
// will return false, since we can't insert safely into a literal block.
// Instead, buffer the messages, and then print when we reach the closing
// tag (in EndElement).
if (!driver_->InsertComment(flush_message)) {
StrAppend(&flush_messages_, flush_message);
}
}
// Capture the flush-durations in the grand totals to be emitted at
// end of document.
parse_.AddToTotal();
render_.AddToTotal();
idle_.AddToTotal();
if (end_document_seen_) {
driver_->InsertComment(
StrCat(ListActiveFiltersAndOptions(),
FormatEndDocumentMessage(
time_since_init_parse_us, parse_.total_us(),
render_.total_us(), idle_.total_us(), num_flushes_,
driver_->is_critical_images_beacon_enabled(),
critical_image_urls_, dynamically_disabled_filter_list_)));
} else {
// We don't count the flush at end-of-document because that is automatically
// called by RewriteDriver/HtmlParse, and is not initiated from upstream,
// e.g. from PHP $flush.
++num_flushes_;
// Restart the idle-time now that the Flush is over.
idle_.Start(now_us);
}
}
void DebugFilter::EndDocument() {
// Despite the tempting symmetry, we can't call idle_.End(...) here because
// this actually gets called during Rendering, when we are not idle.
end_document_seen_ = true;
}
} // namespace net_instaweb