blob: 021262aa149af4e31a552b891ec827d15a3c5ff6 [file] [log] [blame]
/**
* Copyright 2010 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: abliss@google.com (Adam Bliss)
#include "public/add_instrumentation_filter.h"
#include "net/instaweb/htmlparse/public/html_parse.h"
#include "net/instaweb/htmlparse/public/html_element.h"
#include "net/instaweb/util/public/statistics.h"
#include "net/instaweb/util/public/string_util.h"
namespace net_instaweb {
namespace {
// The javascript tag to insert in the top of the <head> element. We want this
// as early as possible in the html. It must be short and fast.
const char kHeadScript[] =
"<script type='text/javascript'>"
"window.mod_pagespeed_start = Number(new Date());"
"</script>";
// The javascript tag to insert at the bottom of document. The %s will be
// replaced with the custom beacon url, by default
// "./mod_pagespeed_beacon?ets=". Then our timing info, e.g. "load:123", will
// be appended.
const char kTailScript[] =
"<script type='text/javascript'>"
"function g(){new Image().src='%sload:'+"
"(Number(new Date())-window.mod_pagespeed_start);};"
"var f=window.addEventListener;if(f){f('load',g,false);}else{"
"f=window.attachEvent;if(f){f('onload',g);}}"
"</script>";
// Timing tag for total page load time. Also embedded in kTailScript above!
const char kLoadTag[] = "load:";
// Variables for the beacon to increment. These are currently handled in
// mod_pagespeed_handler on apache. The average load time in milliseconds is
// total_page_load_ms / page_load_count. Note that these are not updated
// together atomically, so you might get a slightly bogus value.
const char kTotalPageLoadMs[] = "total_page_load_ms";
const char kPageLoadCount[] = "page_load_count";
} // namespace
AddInstrumentationFilter::AddInstrumentationFilter(
HtmlParse* html_parse, const StringPiece& beacon_url, Statistics* stats)
: html_parse_(html_parse),
found_head_(false),
s_head_(html_parse->Intern("head")),
s_body_(html_parse->Intern("body")),
total_page_load_ms_((stats == NULL) ? NULL :
stats->GetVariable(kTotalPageLoadMs)),
page_load_count_((stats == NULL) ? NULL :
stats->GetVariable(kPageLoadCount)) {
beacon_url.CopyToString(&beacon_url_);
}
void AddInstrumentationFilter::Initialize(Statistics* statistics) {
statistics->AddVariable(kTotalPageLoadMs);
statistics->AddVariable(kPageLoadCount);
}
void AddInstrumentationFilter::StartDocument() {
found_head_ = false;
}
void AddInstrumentationFilter::StartElement(HtmlElement* element) {
if (!found_head_) {
if (element->tag() == s_head_) {
found_head_ = true;
// TODO(abliss): add an actual element instead, so other filters can
// rewrite this JS
HtmlCharactersNode* script =
html_parse_->NewCharactersNode(element, kHeadScript);
html_parse_->InsertElementAfterCurrent(script);
}
}
}
void AddInstrumentationFilter::EndElement(HtmlElement* element) {
if (element->tag() == s_body_) {
// We relied on the existence of a <head> element. This should have been
// assured by add_head_filter.
CHECK(found_head_) << "Reached end of document without finding <head>."
" Please turn on the add_head filter.";
std::string tailScript = StringPrintf(kTailScript, beacon_url_.c_str());
HtmlCharactersNode* script =
html_parse_->NewCharactersNode(element, tailScript);
html_parse_->InsertElementBeforeCurrent(script);
}
}
bool AddInstrumentationFilter::HandleBeacon(const StringPiece& unparsed_url) {
if ((total_page_load_ms_ == NULL) || (page_load_count_ == NULL)) {
return false;
}
std::string url = unparsed_url.as_string();
// TODO(abliss): proper query parsing
size_t index = url.find(kLoadTag);
if (index == std::string::npos) {
return false;
}
url = url.substr(index + strlen(kLoadTag));
int value = 0;
if (!StringToInt(url, &value)) {
return false;
}
total_page_load_ms_->Add(value);
page_load_count_->Add(1);
return true;
}
} // namespace net_instaweb