Arquivos
hhvm/hphp/runtime/server/access_log.cpp
T
Ben Maurer ef1e94eee2 Only let one thread fadvise at a time
On Linux, fadvise forces unwritten pages to be flushed to disk before
returning. This means that under heavy disk IO, all threads could be
waiting on the fadvise call.

This diff uses atomic operations to ensure that only one thread can
do IO at any given time. In addition, it makes any fadvise calls
tail the end of the file by 1 MB. This reduces the chance that
fadvise will trigger IO at all.
2013-07-22 11:34:12 -07:00

431 linhas
12 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/server/access_log.h"
#include "hphp/runtime/base/datetime.h"
#include "hphp/runtime/base/timestamp.h"
#include <time.h>
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/runtime/server/server_note.h"
#include "hphp/runtime/server/server_stats.h"
#include "hphp/runtime/server/request_uri.h"
#include "hphp/util/process.h"
#include "hphp/util/atomic.h"
#include "hphp/util/compatibility.h"
#include "hphp/util/timer.h"
#include "hphp/util/util.h"
#include "hphp/runtime/base/hardware_counter.h"
using std::endl;
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
AccessLog::~AccessLog() {
signal(SIGCHLD, SIG_DFL);
for (uint i = 0; i < m_output.size(); ++i) {
if (m_output[i].log) {
if (m_files[i].file[0] == '|') {
pclose(m_output[i].log);
} else {
fclose(m_output[i].log);
}
}
}
}
void AccessLog::init(const string &defaultFormat,
vector<AccessLogFileData> &files,
const string &username) {
Lock l(m_lock);
if (m_initialized) return;
m_initialized = true;
m_defaultFormat = defaultFormat;
m_files = files;
openFiles(username);
}
void AccessLog::init(const string &format,
const string &symLink,
const string &file,
const string &username) {
Lock l(m_lock);
if (m_initialized) return;
m_initialized = true;
m_defaultFormat = format;
if (!file.empty() && !format.empty()) {
m_files.push_back(AccessLogFileData(file, symLink, format));
}
openFiles(username);
}
void AccessLog::openFiles(const string &username) {
assert(m_output.empty() && m_cronOutput.empty());
if (m_files.empty()) return;
for (vector<AccessLogFileData>::const_iterator it = m_files.begin();
it != m_files.end(); ++it) {
const string &file = it->file;
const string &symLink = it->symLink;
assert(!file.empty());
FILE *fp = nullptr;
if (Logger::UseCronolog) {
CronologPtr cl(new Cronolog);
if (strchr(file.c_str(), '%')) {
cl->m_template = file;
cl->setPeriodicity();
cl->m_linkName = symLink;
Cronolog::changeOwner(username, symLink);
} else {
cl->m_file = fopen(file.c_str(), "a");
}
m_cronOutput.push_back(cl);
} else {
if (file[0] == '|') {
string plog = file.substr(1);
fp = popen(plog.c_str(), "w");
} else {
fp = fopen(file.c_str(), "a");
}
if (!fp) {
Logger::Error("Could not open access log file %s", file.c_str());
}
m_output.emplace_back(fp);
}
}
}
void AccessLog::log(Transport *transport, const VirtualHost *vhost) {
assert(transport);
if (!m_initialized) return;
AccessLog::ThreadData *threadData = m_fGetThreadData();
FILE *threadLog = threadData->log;
if (threadLog) {
int bytes = writeLog(transport, vhost, threadLog, m_defaultFormat.c_str());
threadData->flusher.recordWriteAndMaybeDropCaches(threadLog, bytes);
}
if (Logger::UseCronolog) {
for (uint i = 0; i < m_cronOutput.size(); ++i) {
Cronolog &cronOutput = *m_cronOutput[i];
FILE *outFile = cronOutput.getOutputFile();
if (!outFile) continue;
const char *format = m_files[i].format.c_str();
int bytes = writeLog(transport, vhost, outFile, format);
cronOutput.flusher.recordWriteAndMaybeDropCaches(outFile, bytes);
}
} else {
for (uint i = 0; i < m_output.size(); ++i) {
LogFileData& output = m_output[i];
FILE *outFile = output.log;
if (!outFile) continue;
const char *format = m_files[i].format.c_str();
int bytes = writeLog(transport, vhost, outFile, format);
if (m_files[i].file[0] != '|') {
output.flusher.recordWriteAndMaybeDropCaches(outFile, bytes);
}
}
}
}
int AccessLog::writeLog(Transport *transport, const VirtualHost *vhost,
FILE *outFile, const char *format) {
char c;
std::ostringstream out;
while ((c = *format++)) {
if (c != '%') {
out << c;
continue;
}
if (parseConditions(format, transport->getResponseCode())) {
string arg = parseArgument(format);
if (!genField(out, format, transport, vhost, arg)) {
out << "-";
}
} else {
skipField(format);
out << "-";
}
}
out << endl;
string output = out.str();
int nbytes = fprintf(outFile, "%s", output.c_str());
fflush(outFile);
return nbytes;
}
bool AccessLog::parseConditions(const char* &format, int code) {
bool wantMatch = true;
if (*format == '!') {
wantMatch = false;
format++;
} else if (!isdigit(*format)) {
// No conditions
return true;
}
char buf[4];
buf[3] = '\0';
bool matched = false;
while(isdigit(*format)) {
buf[0] = format[0];
buf[1] = format[1];
buf[2] = format[2];
int c = atoi(buf);
if (c == code) {
matched = true;
break;
}
format+=4;
}
while (!(*format == '{' || isalpha(*format))) {
format++;
}
return wantMatch == matched;
}
string AccessLog::parseArgument(const char* &format) {
if (*format != '{') return string();
format++;
const char *start = format;
while (*format != '}') { format++; }
string res(start, format - start);
format++;
return res;
}
void AccessLog::skipField(const char* &format) {
// Skip argument
if (*format == '{') {
while (*format != '}') { format++; }
format++;
}
// Find control letter
while (!isalpha(*format)) { format++; }
// Skip it
format++;
}
static void escape_data(std::ostringstream &out, const char *s, int len)
{
static const char digits[] = "0123456789abcdef";
for (int i = 0; i < len; i++) {
unsigned char uc = *s++;
switch (uc) {
case '"': out << "\\\""; break;
case '\\': out << "\\\\"; break;
case '\b': out << "\\b"; break;
case '\f': out << "\\f"; break;
case '\n': out << "\\n"; break;
case '\r': out << "\\r"; break;
case '\t': out << "\\t"; break;
default:
if (uc >= ' ' && (uc & 127) == uc) {
out << (char)uc;
} else {
out << "\\x" << digits[(uc >> 4) & 15] << digits[(uc >> 0) & 15];
}
break;
}
}
}
bool AccessLog::genField(std::ostringstream &out, const char* &format,
Transport *transport, const VirtualHost *vhost,
const string &arg) {
int responseSize = transport->getResponseSize();
int code = transport->getResponseCode();
while (!isalpha(*format)) { format++; }
char type = *format;
format++;
switch (type) {
case 'b':
if (responseSize == 0) return false;
// Fall through
case 'B':
out << responseSize;
break;
case 'C':
if (arg.empty()) {
return false;
} else {
string cookie = transport->getCookie(arg);
if (cookie.empty()) return false;
escape_data(out, cookie.c_str(), cookie.size());
}
break;
case 'D':
{
struct timespec now;
Timer::GetMonotonicTime(now);
out << gettime_diff_us(transport->getWallTime(), now);
}
break;
case 'd':
{
#ifdef CLOCK_THREAD_CPUTIME_ID
struct timespec now;
gettime(CLOCK_THREAD_CPUTIME_ID, &now);
out << gettime_diff_us(transport->getCpuTime(), now);
#else
return false;
#endif
}
break;
case 'h':
out << transport->getRemoteHost();
break;
case 'i':
if (arg.empty()) return false;
{
string header = transport->getHeader(arg.c_str());
if (header.empty()) return false;
if (vhost && vhost->hasLogFilter() &&
strcasecmp(arg.c_str(), "Referer") == 0) {
out << vhost->filterUrl(header);
} else {
out << header;
}
}
break;
case 'I':
out << transport->getRequestSize();
break;
case 'n':
if (arg.empty()) return false;
{
String note = ServerNote::Get(arg);
if (note.isNull()) return false;
out << note.c_str();
}
break;
case 'r':
{
const char *method = nullptr;
switch (transport->getMethod()) {
case Transport::Method::GET: method = "GET"; break;
case Transport::Method::POST: method = "POST"; break;
case Transport::Method::HEAD: method = "HEAD"; break;
default: break;
}
if (!method) return false;
out << method << " ";
const char *url = transport->getUrl();
if (vhost && vhost->hasLogFilter()) {
out << vhost->filterUrl(url);
} else {
out << url;
}
string httpVersion = transport->getHTTPVersion();
out << " HTTP/" << httpVersion;
}
break;
case 's':
out << code;
break;
case 'S':
// %S is not defined in Apache, we grab it here
{
const std::string &info (transport->getResponseInfo());
if (info.empty()) return false;
out << info;
}
break;
case 't':
{
const char *format;
if (arg.empty()) {
format = "[%d/%b/%Y:%H:%M:%S %z]";
} else {
format = arg.c_str();
}
char buf[256];
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buf, 256, format, timeinfo);
out << buf;
}
break;
case 'T':
out << TimeStamp::Current() - m_fGetThreadData()->startTime;
break;
case 'U':
{
String b, q;
RequestURI::splitURL(transport->getUrl(), b, q);
out << b.c_str();
}
break;
case 'v':
{
string host = transport->getHeader("Host");
const string &sname = VirtualHost::GetCurrent()->serverName(host);
if (sname.empty() || RuntimeOption::ForceServerNameToHeader) {
out << host;
} else {
out << sname;
}
}
break;
case 'Y':
{
int64_t now = HardwareCounter::GetInstructionCount();
out << now - transport->getInstructions();
}
break;
case 'y':
out << ServerStats::Get("page.inst.psp");
break;
case 'Z':
out << ServerStats::Get("page.wall.psp");
break;
case 'z':
out << ServerStats::Get("page.cpu.psp");
break;
default:
return false;
}
return true;
}
void AccessLog::onNewRequest() {
if (!m_initialized) return;
ThreadData *threadData = m_fGetThreadData();
threadData->startTime = TimeStamp::Current();
}
bool AccessLog::setThreadLog(const char *file) {
return (m_fGetThreadData()->log = fopen(file, "a")) != nullptr;
}
void AccessLog::clearThreadLog() {
FILE* &threadLog = m_fGetThreadData()->log;
if (threadLog) {
fclose(threadLog);
}
threadLog = nullptr;
}
///////////////////////////////////////////////////////////////////////////////
}