blob: 43d4ff4039bf0115ca286a1cbc87da8a92c396cd [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: jmarantz@google.com (Joshua Marantz)
#include "pagespeed/kernel/base/statistics.h"
#include <limits>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/string_writer.h"
#include "pagespeed/kernel/base/writer.h"
namespace {
// As we do fix size buckets, each bucket has the same height.
const double kBarHeightPerBucket = 20;
// Each bucket has different width, depends on the percentage of bucket value
// out of total counts.
// The width of a bucket is percentage_of_bucket_value * kBarWidthTotal.
const double kBarWidthTotal = 400;
} // namespace
namespace net_instaweb {
class MessageHandler;
Variable::~Variable() {
}
UpDownCounter::~UpDownCounter() {
}
int64 UpDownCounter::SetReturningPreviousValue(int64 value) {
int64 previous_value = Get();
Set(value);
return previous_value;
}
MutexedScalar::~MutexedScalar() {
}
int64 MutexedScalar::Get() const {
if (mutex() != NULL) {
ScopedMutex hold_lock(mutex());
return GetLockHeld();
} else {
return -1;
}
}
void MutexedScalar::Set(int64 new_value) {
if (mutex() != NULL) {
ScopedMutex hold_lock(mutex());
SetLockHeld(new_value);
}
}
int64 MutexedScalar::SetReturningPreviousValue(int64 new_value) {
if (mutex() != NULL) {
ScopedMutex hold_lock(mutex());
return SetReturningPreviousValueLockHeld(new_value);
} else {
return -1;
}
}
int64 MutexedScalar::AddHelper(int64 delta) {
if (mutex() != NULL) {
ScopedMutex hold_lock(mutex());
return AddLockHeld(delta);
} else {
return -1;
}
}
void MutexedScalar::SetLockHeld(int64 new_value) {
SetReturningPreviousValueLockHeld(new_value);
}
int64 MutexedScalar::AddLockHeld(int64 delta) {
int64 value = GetLockHeld() + delta;
SetLockHeld(value);
return value;
}
Histogram::~Histogram() {
}
CountHistogram::CountHistogram(AbstractMutex* mutex)
: mutex_(mutex), count_(0) {}
CountHistogram::~CountHistogram() {
}
TimedVariable::~TimedVariable() {
}
FakeTimedVariable::FakeTimedVariable(StringPiece name, Statistics* stats)
: var_(stats->AddVariable(name)) {
}
FakeTimedVariable::~FakeTimedVariable() {
}
void Histogram::WriteRawHistogramData(Writer* writer, MessageHandler* handler) {
const char bucket_style[] = "<tr><td style=\"padding: 0 0 0 0.25em\">"
"[</td><td style=\"text-align:right;padding:0 0.25em 0 0\">"
"%s,</td><td style=text-align:right;padding: 0 0.25em\">%s)</td>";
const char value_style[] = "<td style=\"text-align:right;padding:0 0.25em\">"
"%.f</td>";
const char perc_style[] = "<td style=\"text-align:right;padding:0 0.25em\">"
"%.1f%%</td>";
const char bar_style[] = "<td><div style=\"width: %.fpx;height:%.fpx;"
"background-color:blue\"></div></td>";
double count = CountInternal();
double perc = 0;
double cumulative_perc = 0;
// Write prefix of the table.
writer->Write("<table>", handler);
for (int i = 0, n = NumBuckets(); i < n; ++i) {
double value = BucketCount(i);
if (value == 0) {
// We do not draw empty bucket.
continue;
}
double lower_bound = BucketStart(i);
double upper_bound = BucketLimit(i);
GoogleString lower_bound_string = StringPrintf("%.0f", lower_bound);
if (lower_bound == -std::numeric_limits<double>::infinity()) {
lower_bound_string = "-&infin;";
}
GoogleString upper_bound_string = StringPrintf("%.0f", upper_bound);
if (upper_bound == std::numeric_limits<double>::infinity()) {
upper_bound_string = "&infin;";
}
perc = value * 100 / count;
cumulative_perc += perc;
GoogleString output = StrCat(
StringPrintf(bucket_style, lower_bound_string.c_str(),
upper_bound_string.c_str()),
StringPrintf(value_style, value),
StringPrintf(perc_style, perc),
StringPrintf(perc_style, cumulative_perc),
StringPrintf(bar_style, (perc * kBarWidthTotal) / 100,
kBarHeightPerBucket));
writer->Write(output, handler);
}
// Write suffix of the table.
writer->Write("</table>", handler);
}
void Histogram::Render(int index, Writer* writer, MessageHandler* handler) {
writer->Write(StringPrintf("<div id='hist_%d' style='display:none'>", index),
handler);
// Don't hold a lock while calling the writer, as this can deadlock if
// the writer itself winds up invoking pagespeed, causing the histogram
// to be locked. So buffer each histogram and release the lock before
// passing it to writer.
GoogleString buf;
{
ScopedMutex hold(lock());
StringWriter string_writer(&buf);
WriteRawHistogramData(&string_writer, handler);
}
writer->Write(buf, handler);
writer->Write("</div>\n", handler);
}
Statistics::~Statistics() {
}
UpDownCounter* Statistics::AddGlobalUpDownCounter(const StringPiece& name) {
return AddUpDownCounter(name);
}
namespace {
const char kHistogramProlog[] =
"<div>\n"
" <table>\n"
" <thead><tr>\n"
" <td>Histogram Name (click to view)</td>\n"
" <td>Count</td>\n"
" <td>Avg</td>\n"
" <td>StdDev</td>\n"
" <td>Min</td>\n"
" <td>Median</td>\n"
" <td>Max</td>\n"
" <td>90%</td>\n"
" <td>95%</td>\n"
" <td>99%</td>\n"
" </tr></thead><tbody>\n";
const char kHistogramRowFormat[] =
" <tr id='hist_row_%d'>\n"
" <td><label><input type='radio' name='choose_histogram'%s\n"
" onchange='setHistogram(%d)'>%s</label></td>\n"
" <td>%.0f</td><td>%.1f</td><td>%.1f</td>\n" // count, avg, stddev
" <td>%.0f</td><td>%.0f</td><td>%.0f</td>\n" // min, median, max
" <td>%.0f</td><td>%.0f</td><td>%.0f</td>\n" // 90%, 95%, 99%
" </tr>\n";
const char kHistogramEpilog[] =
" </tbody>\n"
" </table>\n"
"</div>\n";
const char kHistogramScript[] =
"<script>\n"
" var currentHistogram = -1;\n"
" function setHistogram(id) {\n"
" var div = document.getElementById('hist_' + currentHistogram);\n"
" if (div) {\n"
" div.style.display = 'none';\n"
" }\n"
" div = document.getElementById('hist_' + id);\n"
" if (div) {\n"
" div.style.display = '';\n"
" }\n"
" var row = document.getElementById('hist_row_' + currentHistogram);\n"
" if (row) {\n"
" row.style.backgroundColor = 'white';\n"
" }\n"
" row = document.getElementById('hist_row_' + id);\n"
" if (row) {\n"
" row.style.backgroundColor = 'yellow';\n"
" }\n"
" currentHistogram = id;\n"
" }\n"
" setHistogram(0);\n"
"</script>\n";
} // namespace
void Statistics::RenderHistograms(Writer* writer, MessageHandler* handler) {
StringVector hist_names = HistogramNames(); // includes empty ones.
StringVector populated_histogram_names;
std::vector<Histogram*> populated_histograms;
// Find non-empty histograms. Note that when the server first comes
// up, there won't be any data in the histograms because there is no
// traffic. Other histograms may never be populated depending on
// mod_pagespeed settings. We pre-scan the histograms, capturing a
// snapshot of the non-empty ones, because a histogram might become
// non-empty asynchronously between the next two loops, and that
// would skew indexing.
for (int i = 0, n = hist_names.size(); i < n; ++i) {
Histogram* hist = FindHistogram(hist_names[i]);
// Exclude histograms with zero count.
if (hist->Count() != 0) {
populated_histograms.push_back(hist);
populated_histogram_names.push_back(hist_names[i]);
}
}
writer->Write("<hr/>", handler);
// Write table data for each histogram.
if (populated_histograms.empty()) {
writer->Write("<em>No histogram data yet. Refresh once there is "
"traffic.</em>", handler);
} else {
// Write the table header for all histograms.
writer->Write(StringPiece(kHistogramProlog,
STATIC_STRLEN(kHistogramProlog)),
handler);
// Write a row of the table data for each non-empty histogram.
CHECK_EQ(populated_histogram_names.size(), populated_histograms.size());
for (int i = 0, n = populated_histograms.size(); i < n; ++i) {
Histogram* hist = populated_histograms[i];
GoogleString row = hist->HtmlTableRow(populated_histogram_names[i], i);
writer->Write(row, handler);
}
writer->Write(StringPiece(kHistogramEpilog,
STATIC_STRLEN(kHistogramEpilog)),
handler);
// Render the non-empty histograms.
for (int i = 0, n = populated_histograms.size(); i < n; ++i) {
populated_histograms[i]->Render(i, writer, handler);
}
// Write the JavaScript to display the histograms and highlight the row
// when selected.
writer->Write(StringPiece(kHistogramScript,
STATIC_STRLEN(kHistogramScript)),
handler);
}
writer->Write("<hr/>\n", handler);
}
GoogleString Histogram::HtmlTableRow(const GoogleString& title, int index) {
ScopedMutex hold(lock());
return StringPrintf(
kHistogramRowFormat,
index,
(index == 0) ? " selected" : "",
index,
title.c_str(),
CountInternal(),
AverageInternal(),
StandardDeviationInternal(),
MinimumInternal(),
PercentileInternal(50),
MaximumInternal(),
PercentileInternal(90),
PercentileInternal(95),
PercentileInternal(99));
}
void Statistics::RenderTimedVariables(Writer* writer,
MessageHandler* message_handler) {
TimedVariable* timedvar = NULL;
const GoogleString end("</table>\n<td>\n<td>\n");
std::map<GoogleString, StringVector> group_map = TimedVariableMap();
std::map<GoogleString, StringVector>::const_iterator p;
// Export statistics in each group in one table.
for (p = group_map.begin(); p != group_map.end(); ++p) {
// Write table header for each group.
const GoogleString begin = StrCat(
"<p><table bgcolor=#eeeeff width=100%%>",
"<tr align=center><td><font size=+2>", p->first,
"</font></td></tr></table>",
"</p>\n<td>\n<td>\n<td>\n<td>\n<td>\n",
"<table bgcolor=#fff5ee frame=box cellspacing=1 cellpadding=2>\n",
"<tr bgcolor=#eee5de><td>"
"<form action=\"/statusz/reset\" method = \"post\">"
"<input type=\"submit\" value = \"Reset Statistics\"></form></td>"
"<th align=right>TenSec</th><th align=right>Minute</th>"
"<th align=right>Hour</th><th align=right>Total</th></tr>");
writer->Write(begin, message_handler);
// Write each statistic as a row in the table.
for (int i = 0, n = p->second.size(); i < n; ++i) {
timedvar = FindTimedVariable(p->second[i]);
const GoogleString content = StringPrintf("<tr><td> %s </td>"
"<td align=right> %s </td><td align=right> %s </td>"
"<td align=right> %s </td><td align=right> %s </td></tr>",
p->second[i].c_str(),
Integer64ToString(timedvar->Get(TimedVariable::TENSEC)).c_str(),
Integer64ToString(timedvar->Get(TimedVariable::MINUTE)).c_str(),
Integer64ToString(timedvar->Get(TimedVariable::HOUR)).c_str(),
Integer64ToString(timedvar->Get(TimedVariable::START)).c_str());
writer->Write(content, message_handler);
}
// Write table ending part.
writer->Write(end, message_handler);
}
}
int64 Statistics::LookupValue(StringPiece stat_name) {
Variable* var = FindVariable(stat_name);
if (var != NULL) {
return var->Get();
}
UpDownCounter* counter = FindUpDownCounter(stat_name);
if (counter != NULL) {
return counter->Get();
}
TimedVariable* tvar = FindTimedVariable(stat_name);
if (tvar != NULL) {
return tvar->Get(TimedVariable::START);
}
LOG(FATAL) << "Could not find stat: " << stat_name;
return 0;
}
} // namespace net_instaweb