Return-Path: X-Original-To: apmail-httpd-cvs-archive@www.apache.org Delivered-To: apmail-httpd-cvs-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 034A11194F for ; Thu, 1 May 2014 11:46:58 +0000 (UTC) Received: (qmail 27681 invoked by uid 500); 1 May 2014 11:45:58 -0000 Delivered-To: apmail-httpd-cvs-archive@httpd.apache.org Received: (qmail 27345 invoked by uid 500); 1 May 2014 11:45:42 -0000 Mailing-List: contact cvs-help@httpd.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@httpd.apache.org list-help: list-unsubscribe: List-Post: List-Id: Delivered-To: mailing list cvs@httpd.apache.org Received: (qmail 27217 invoked by uid 99); 1 May 2014 11:45:37 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 May 2014 11:45:37 +0000 X-ASF-Spam-Status: No, hits=-1996.5 required=5.0 tests=ALL_TRUSTED,URIBL_BLACK X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 May 2014 11:45:32 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 448152388C7C; Thu, 1 May 2014 11:44:05 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1591622 [27/33] - in /httpd/mod_spdy/trunk: ./ base/ base/base.xcodeproj/ base/metrics/ build/ build/all.xcodeproj/ build/build_util.xcodeproj/ build/install.xcodeproj/ build/internal/ build/linux/ build/mac/ build/util/ build/win/ install... Date: Thu, 01 May 2014 11:43:45 -0000 To: cvs@httpd.apache.org From: jim@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140501114405.448152388C7C@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,125 @@ +/* + * Copyright 2011 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: fangfei@google.com (Fangfei Zhou) + +#include "net/instaweb/util/public/shared_circular_buffer.h" + +#include +#include "base/scoped_ptr.h" +#include "net/instaweb/util/public/abstract_mutex.h" +#include "net/instaweb/util/public/abstract_shared_mem.h" +#include "net/instaweb/util/public/circular_buffer.h" +#include "net/instaweb/util/public/message_handler.h" +#include "net/instaweb/util/public/string.h" +#include "net/instaweb/util/public/string_util.h" +#include "net/instaweb/util/public/writer.h" + +namespace { + const char kSharedCircularBufferObjName[] = "SharedCircularBuffer"; +} // namespace + +namespace net_instaweb { + +SharedCircularBuffer::SharedCircularBuffer(AbstractSharedMem* shm_runtime, + const int buffer_capacity, + const GoogleString& filename_prefix, + const GoogleString& filename_suffix) + : shm_runtime_(shm_runtime), + buffer_capacity_(buffer_capacity), + filename_prefix_(filename_prefix), + filename_suffix_(filename_suffix) { +} + +SharedCircularBuffer::~SharedCircularBuffer() { +} + +// Initialize shared mutex. +bool SharedCircularBuffer::InitMutex(MessageHandler* handler) { + if (!segment_->InitializeSharedMutex(0, handler)) { + handler->Message( + kError, "Unable to create mutex for shared memory circular buffer"); + return false; + } + return true; +} + +bool SharedCircularBuffer::InitSegment(bool parent, + MessageHandler* handler) { + // Size of segment includes mutex and circular buffer. + int buffer_size = CircularBuffer::Sizeof(buffer_capacity_); + size_t total = shm_runtime_->SharedMutexSize() + buffer_size; + if (parent) { + // In root process -> initialize the shared memory. + segment_.reset( + shm_runtime_->CreateSegment(SegmentName(), total, handler)); + // Initialize mutex. + if (segment_.get() != NULL && !InitMutex(handler)) { + segment_.reset(NULL); + shm_runtime_->DestroySegment(SegmentName(), handler); + return false; + } + } else { + // In child process -> attach to exisiting segment. + segment_.reset( + shm_runtime_->AttachToSegment(SegmentName(), total, handler)); + if (segment_.get() == NULL) { + return false; + } + } + // Attach Mutex. + mutex_.reset(segment_->AttachToSharedMutex(0)); + // Initialize the circular buffer. + int pos = shm_runtime_->SharedMutexSize(); + buffer_ = CircularBuffer::Init( + parent, + static_cast(const_cast(segment_->Base() + pos)), + buffer_size, buffer_capacity_); + return true; +} + +void SharedCircularBuffer::Clear() { + ScopedMutex hold_lock(mutex_.get()); + buffer_->Clear(); +} + +bool SharedCircularBuffer::Write(const StringPiece& message) { + ScopedMutex hold_lock(mutex_.get()); + return buffer_->Write(message); +} + +bool SharedCircularBuffer::Dump(Writer* writer, MessageHandler* handler) { + ScopedMutex hold_lock(mutex_.get()); + return (writer->Write(buffer_->ToString(handler), handler)); +} + +GoogleString SharedCircularBuffer::ToString(MessageHandler* handler) { + ScopedMutex hold_lock(mutex_.get()); + return buffer_->ToString(handler); +} + +void SharedCircularBuffer::GlobalCleanup(MessageHandler* handler) { + if (segment_.get() != NULL) { + shm_runtime_->DestroySegment(SegmentName(), handler); + } +} + +GoogleString SharedCircularBuffer::SegmentName() const { + return StrCat(filename_prefix_, kSharedCircularBufferObjName, ".", + filename_suffix_); +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer_test_base.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer_test_base.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer_test_base.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_circular_buffer_test_base.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,169 @@ +// Copyright 2011 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: fangfei@google.com (Fangfei Zhou) + +#include "base/scoped_ptr.h" +#include "net/instaweb/util/public/function.h" +#include "net/instaweb/util/public/gtest.h" +#include "net/instaweb/util/public/mock_message_handler.h" +#include "net/instaweb/util/public/shared_circular_buffer.h" +#include "net/instaweb/util/public/shared_circular_buffer_test_base.h" +#include "net/instaweb/util/public/shared_mem_test_base.h" +#include "net/instaweb/util/public/string_util.h" + +namespace net_instaweb { + +namespace { +const int kBufferSize = 10; +const char kPrefix[] = "/prefix/"; +const char kPostfix[] = "postfix"; +const char kString[] = "012"; +} // namespace + +SharedCircularBufferTestBase::SharedCircularBufferTestBase( + SharedMemTestEnv* test_env) + : test_env_(test_env), + shmem_runtime_(test_env->CreateSharedMemRuntime()) { +} + +bool SharedCircularBufferTestBase::CreateChild(TestMethod method) { + Function* callback = + new MemberFunction0(method, this); + return test_env_->CreateChild(callback); +} + +SharedCircularBuffer* SharedCircularBufferTestBase::ChildInit() { + SharedCircularBuffer* buff = + new SharedCircularBuffer(shmem_runtime_.get(), kBufferSize, kPrefix, + kPostfix); + buff->InitSegment(false, &handler_); + return buff; +} + +SharedCircularBuffer* SharedCircularBufferTestBase::ParentInit() { + SharedCircularBuffer* buff = + new SharedCircularBuffer(shmem_runtime_.get(), kBufferSize, kPrefix, + kPostfix); + buff->InitSegment(true, &handler_); + return buff; +} + +// Basic initialization/writing/cleanup test +void SharedCircularBufferTestBase::TestCreate() { + // Create buffer from root Process. + scoped_ptr buff(ParentInit()); + buff->Write("parent"); + EXPECT_EQ("parent", buff->ToString(&handler_)); + ASSERT_TRUE(CreateChild(&SharedCircularBufferTestBase::TestCreateChild)); + test_env_->WaitForChildren(); + // After the child process writes to buffer, + // the content should be updated. + EXPECT_EQ("parentkid", buff->ToString(&handler_)); + buff->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedCircularBufferTestBase::TestCreateChild() { + scoped_ptr buff(ChildInit()); + // Child writes to buffer. + if (!buff->Write("kid")) { + test_env_->ChildFailed(); + } +} + +void SharedCircularBufferTestBase::TestAdd() { + // Every child process writes "012" to buffer. + scoped_ptr buff(ParentInit()); + for (int i = 0; i < 2; ++i) { + ASSERT_TRUE(CreateChild(&SharedCircularBufferTestBase::TestAddChild)); + } + test_env_->WaitForChildren(); + EXPECT_EQ("012012", buff->ToString(&handler_)); + + buff->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedCircularBufferTestBase::TestAddChild() { + scoped_ptr buff(ChildInit()); + buff->Write("012"); +} + +void SharedCircularBufferTestBase::TestClear() { + // We can clear things from the child + scoped_ptr buff(ParentInit()); + // Write a string to buffer. + buff->Write("012"); + EXPECT_EQ("012", buff->ToString(&handler_)); + ASSERT_TRUE(CreateChild(&SharedCircularBufferTestBase::TestClearChild)); + test_env_->WaitForChildren(); + // Now the buffer should be empty as the child cleared it. + EXPECT_EQ("", buff->ToString(&handler_)); + buff->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedCircularBufferTestBase::TestClearChild() { + scoped_ptr buff(ChildInit()); + buff->InitSegment(false, &handler_); + buff->Clear(); +} + +void SharedCircularBufferTestBase::TestChildWrite() { + scoped_ptr buff(ChildInit()); + buff->InitSegment(false, &handler_); + buff->Write(message_); +} + +void SharedCircularBufferTestBase::TestChildBuff() { + scoped_ptr buff(ChildInit()); + buff->InitSegment(false, &handler_); + // Check if buffer content is correct. + if (expected_result_ != buff->ToString(&handler_)) { + test_env_->ChildFailed(); + } +} + +// Check various operations, and wraparound, with multiple processes. +void SharedCircularBufferTestBase::TestCircular() { + scoped_ptr parent(ParentInit()); + parent->Clear(); + // Write in parent process. + parent->Write("012345"); + EXPECT_EQ("012345", parent->ToString(&handler_)); + // Write in a child process. + message_ = "67"; + ASSERT_TRUE(CreateChild( + &SharedCircularBufferTestBase::TestChildWrite)); + test_env_->WaitForChildren(); + EXPECT_EQ("01234567", parent->ToString(&handler_)); + // Write in parent process. + parent->Write("89"); + // Check buffer content in a child process. + // Buffer size is 10. It should be filled exactly so far. + expected_result_ = "0123456789"; + ASSERT_TRUE(CreateChild( + &SharedCircularBufferTestBase::TestChildBuff)); + test_env_->WaitForChildren(); + // Lose the first char. + parent->Write("a"); + EXPECT_EQ("123456789a", parent->ToString(&handler_)); + // Write a message with length larger than buffer. + parent->Write("bcdefghijkl"); + EXPECT_EQ("cdefghijkl", parent->ToString(&handler_)); + parent->GlobalCleanup(&handler_); +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,298 @@ +/* + * Copyright 2011 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: jhoch@google.com (Jason Hoch) + +#include "net/instaweb/util/public/shared_dynamic_string_map.h" + +#include "base/logging.h" +#include "net/instaweb/util/public/basictypes.h" +#include "net/instaweb/util/public/rolling_hash.h" +#include "net/instaweb/util/public/string_util.h" +#include "net/instaweb/util/public/writer.h" + +namespace net_instaweb { + +namespace { + +const int kTableFactor = 2; +const char kSharedDynamicStringMapSegmentName[] = "SharedDynamicStringMap"; +const size_t kPointerSize = sizeof(char*); // NOLINT +const size_t kOffsetSize = sizeof(size_t); // NOLINT +const size_t kIntSize = sizeof(int); // NOLINT +const size_t kEntrySize = sizeof(Entry); // NOLINT + +} // namespace + +SharedDynamicStringMap::SharedDynamicStringMap( + size_t number_of_strings, + size_t average_string_length, + AbstractSharedMem* shm_runtime, + const GoogleString& filename_prefix, + const GoogleString& filename_suffix) + : number_of_strings_(NextPowerOfTwo(number_of_strings)), + average_string_length_(average_string_length), + segment_name_(StrCat(filename_prefix, + kSharedDynamicStringMapSegmentName, + ".", + filename_suffix)), + shm_runtime_(shm_runtime) { + // Check to make sure number_of_strings_ is a power of 2. + DCHECK_EQ(static_cast(0), + number_of_strings_ & (number_of_strings_ - 1)); + mutex_size_ = shm_runtime_->SharedMutexSize(); + table_size_ = number_of_strings_ * kTableFactor; + // (See drawing in header file for further explanation) + // First we have (table_size_ + 1) mutexes. + // Then we have the strings, which are inserted one at a time and are + // of variable size. + // Then we have the string_offset of the next string to be inserted. + // Then we have the number of strings inserted. + // Finally we have the table_size_ entries that contain values and string + // offsets. + mutex_offset_ = 0; + strings_offset_ = mutex_size_ * (table_size_ + 1); + string_offset_offset_ = + strings_offset_ + number_of_strings * average_string_length_; + number_inserted_offset_ = string_offset_offset_ + kOffsetSize; + table_offset_ = number_inserted_offset_ + kIntSize; + total_size_ = table_offset_ + table_size_ * kEntrySize; +} + +bool SharedDynamicStringMap::InitSegment(bool parent, + MessageHandler* message_handler) { + // Pointer returned by segment_->Base() is a char* + // Table contains a pointer and an int for each entry. + bool ok = true; + if (parent) { + // Initialize shared memory + segment_.reset(shm_runtime_->CreateSegment(segment_name_, + total_size_, + message_handler)); + if (segment_.get() == NULL) { + ok = false; + } else { + // Initialize mutexes - there is an extra mutex, the last one, shared + // by the string_offset and number_inserted values known as the "insert + // string mutex" + for (int i = 0; i < static_cast(table_size_) + 1; i++) { + if (!segment_->InitializeSharedMutex(i * mutex_size_, + message_handler)) { + ok = false; + break; + } + } + } + } else { + // In child process -> attach to existing segment + segment_.reset(shm_runtime_->AttachToSegment(segment_name_, + total_size_, + message_handler)); + if (segment_.get() == NULL) { + ok = false; + } + } + if (ok) { + insert_string_mutex_.reset(GetMutex(table_size_)); + } else { + ClearSegment(message_handler); + } + return ok; +} + +void SharedDynamicStringMap::ClearSegment(MessageHandler* message_handler) { + segment_.reset(NULL); + shm_runtime_->DestroySegment(segment_name_, message_handler); +} + +int SharedDynamicStringMap::IncrementElement(const StringPiece& string) { + if (segment_.get() == NULL) { + return 0; + } + Entry* entry_pointer = 0; + // We need to lock the entry for incrementation + int entry = FindEntry(string, true, &entry_pointer); + int value; + // If entry is -1 then the table is full. + if (entry == -1) { + value = 0; + } else { + // The mutex for the entry is locked by FindEntry for continued use + scoped_ptr mutex(GetMutex(entry)); + if (entry_pointer->value == 0) { + // The string is not yet in the table. + value = InsertString(string, entry_pointer); + } else { + // The string is already in the table. + value = ++(entry_pointer->value); + } + mutex->Unlock(); + } + return value; +} + +int SharedDynamicStringMap::LookupElement(const StringPiece& string) const { + if (segment_.get() == NULL) { + return 0; + } + Entry* entry_pointer = 0; + // We don't need to lock the entry for lookup + int entry = FindEntry(string, false, &entry_pointer); + // If entry is -1 then the table is full. + return (entry == -1) ? 0 : entry_pointer->value; +} + +int SharedDynamicStringMap::FindEntry(const StringPiece& string, + bool lock, + Entry** entry_pointer_pointer) const { + // lock should always be set to true for writes, and having lock set to false + // for a read can occasionally result in a mistake - see header file. + uint64 hash = RollingHash(string.data(), 0, string.size()); + // First 32 bits + uint32 hash1 = hash >> 32; + // Second 32 bits + uint32 hash2 = hash; + // For now we assume table_size_ is a power of two, so having hash2 be odd + // makes sure that our secondary hashing cycles through all table entries + hash2 |= 1; + // hash1 dictates starting entry + size_t entry = hash1 % table_size_; + size_t starting_entry = entry; + scoped_ptr mutex; + do { + // Lock this entry + mutex.reset(GetMutex(entry)); + if (lock) { + mutex->Lock(); + } + // Table contains value and then pointer to char + *entry_pointer_pointer = GetEntry(entry); + char* entry_string_data = + GetStringAtOffset((*entry_pointer_pointer)->string_offset); + if ((*entry_pointer_pointer)->value == 0) { + // If the value is 0 the string is not yet in the table and/or this is the + // place to insert it. + return entry; + } else if (strcmp(entry_string_data, string.data()) == 0) { + // We've found the string + return entry; + } else { + // Use secondary hashing to proceed to the next entry + entry += hash2; + // Same as entry %= table_size_ since table_size_ is a power of 2. + entry &= (table_size_ - 1); + } + if (lock) { + mutex->Unlock(); + } + } while (entry != starting_entry); + // If the above condition fails, the table is full. + return -1; +} + +Entry* SharedDynamicStringMap::GetEntry(size_t n) const { + Entry* first_entry = SharedDynamicStringMap::GetFirstEntry(); + return first_entry + n; +} + +Entry* SharedDynamicStringMap::GetFirstEntry() const { + return reinterpret_cast( + const_cast(segment_->Base() + table_offset_)); +} + +AbstractMutex* SharedDynamicStringMap::GetMutex(size_t n) const { + return segment_->AttachToSharedMutex(mutex_offset_ + n * mutex_size_); +} + +int SharedDynamicStringMap::InsertString(const StringPiece& string, + Entry* entry_pointer) { + // We store the offset of the next string to be inserted at + // string_offset_offset_, which has an associated mutex + ScopedMutex mutex(insert_string_mutex_.get()); + size_t* string_offset = reinterpret_cast( + const_cast(segment_->Base() + string_offset_offset_)); + size_t size = string.size(); + // If we can't insert the string then we return 0; + if (strings_offset_ + *string_offset + size >= table_offset_) { + return 0; + } + char* inserted_string = GetStringAtOffset(*string_offset); + memcpy(inserted_string, string.data(), size); + // After copying the string we add a terminating null character + *(inserted_string + size) = '\0'; + // Store the offset in the entry + entry_pointer->string_offset = *string_offset; + // +1 for the terminating null character + *string_offset += size + 1; + // Increment the number inserted + int* number_inserted = reinterpret_cast( + const_cast(segment_->Base() + number_inserted_offset_)); + ++(*number_inserted); + // Finally, increment the entry value + return ++(entry_pointer->value); +} + +char* SharedDynamicStringMap::GetStringAtOffset(size_t offset) const { + return const_cast(segment_->Base() + strings_offset_ + offset); +} + +void SharedDynamicStringMap::GetKeys(StringSet* strings) { + int number_inserted = GetNumberInserted(); + char* string = const_cast(segment_->Base() + strings_offset_); + for (int i = 0; i < number_inserted; i++) { + strings->insert(string); + int size = strlen(string); + string += size + 1; + } +} + +int SharedDynamicStringMap::GetNumberInserted() const { + return *(reinterpret_cast( + const_cast (segment_->Base() + number_inserted_offset_))); +} + +void SharedDynamicStringMap::GlobalCleanup(MessageHandler* message_handler) { + if (segment_.get() != NULL) { + shm_runtime_->DestroySegment(segment_name_, message_handler); + } +} + +void SharedDynamicStringMap::Dump(Writer* writer, + MessageHandler* message_handler) { + int number_inserted = GetNumberInserted(); + char* string = const_cast(segment_->Base() + strings_offset_); + for (int i = 0; i < number_inserted; i++) { + int value = LookupElement(string); + writer->Write(string, message_handler); + writer->Write(": ", message_handler); + writer->Write(IntegerToString(value), message_handler); + writer->Write("\n", message_handler); + int size = strlen(string); + string += size + 1; + } +} + +size_t SharedDynamicStringMap::NextPowerOfTwo(size_t n) { + if (n == 0) { + return 1; + } + n--; + for (int shift = 1; shift < static_cast(kOffsetSize); shift *= 2) + n |= n >> shift; + return n + 1; +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map_test_base.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map_test_base.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map_test_base.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_dynamic_string_map_test_base.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,269 @@ +// Copyright 2011 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: jhoch@google.com (Jason Hoch) + +#include "net/instaweb/util/public/shared_dynamic_string_map_test_base.h" + +#include +#include +#include "base/logging.h" +#include "net/instaweb/util/public/function.h" +#include "net/instaweb/util/public/shared_dynamic_string_map.h" +#include "net/instaweb/util/public/string_util.h" +#include "net/instaweb/util/public/string_writer.h" + +namespace net_instaweb { + +namespace { + +const int kIntSize = sizeof(int); // NOLINT +// Should be a multiple of 4 +const int kTableSize = 1024; +// +1 for string that causes overflow +const int kNumberOfStrings = kTableSize + 1; +const int kStringSize = 64; +const char kPrefix[] = "/prefix/"; +const char kSuffix[] = "suffix"; +const char kExampleString1[] = "http://www.example1.com"; +const char kExampleString2[] = "http://www.example2.com"; + +} // namespace + +SharedDynamicStringMapTestBase::SharedDynamicStringMapTestBase( + SharedMemTestEnv* test_env) + : test_env_(test_env), + shmem_runtime_(test_env->CreateSharedMemRuntime()) { + // We must be able to fit a unique int in our string + CHECK(2 * kIntSize < kStringSize - 1); + // 255 because we can't use null char + CHECK(kNumberOfStrings < pow(16, 2 * kIntSize)); + // After the int at the front we add random chars + for (int i = 0; i < kNumberOfStrings; i++) { + // We pad the beginning with the hex representation of i, a unique string + // or non-null characters + GoogleString string = StringPrintf("%0*x", 2 * kIntSize, i); + // We fill the rest of the string with random lower-case letters + // -1 so there's room for the terminating null character + while (string.length() < kStringSize - 1) { + string.push_back(random() % 26 + 'a'); + } + strings_.push_back(string); + } +} + +bool SharedDynamicStringMapTestBase::CreateChild(TestMethod0 method) { + Function* callback = + new MemberFunction0(method, this); + return test_env_->CreateChild(callback); +} + +bool SharedDynamicStringMapTestBase::CreateFillChild(TestMethod2 method, + int start, + int number_of_strings) { + Function* callback = + new MemberFunction2( + method, this, start, number_of_strings); + return test_env_->CreateChild(callback); +} + +SharedDynamicStringMap* SharedDynamicStringMapTestBase::ChildInit() { + SharedDynamicStringMap* map = + new SharedDynamicStringMap(kTableSize, + kStringSize, + shmem_runtime_.get(), + kPrefix, + kSuffix); + map->InitSegment(false, &handler_); + return map; +} + +SharedDynamicStringMap* SharedDynamicStringMapTestBase::ParentInit() { + SharedDynamicStringMap* map = + new SharedDynamicStringMap(kTableSize, + kStringSize, + shmem_runtime_.get(), + kPrefix, + kSuffix); + map->InitSegment(true, &handler_); + return map; +} + +void SharedDynamicStringMapTestBase::TestSimple() { + scoped_ptr map(ParentInit()); + GoogleString output; + StringWriter writer(&output); + map->Dump(&writer, &handler_); + EXPECT_EQ(output, ""); + EXPECT_EQ(0, map->GetNumberInserted()); + map->IncrementElement(kExampleString1); + EXPECT_EQ(1, map->LookupElement(kExampleString1)); + output.clear(); + map->Dump(&writer, &handler_); + EXPECT_EQ(output, "http://www.example1.com: 1\n"); + EXPECT_EQ(1, map->GetNumberInserted()); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::TestCreate() { + scoped_ptr map(ParentInit()); + EXPECT_EQ(0, map->LookupElement(kExampleString1)); + EXPECT_EQ(0, map->LookupElement(kExampleString2)); + EXPECT_EQ(0, map->GetNumberInserted()); + map->IncrementElement(kExampleString1); + map->IncrementElement(kExampleString2); + EXPECT_EQ(1, map->LookupElement(kExampleString1)); + EXPECT_EQ(1, map->LookupElement(kExampleString2)); + EXPECT_EQ(2, map->GetNumberInserted()); + ASSERT_TRUE(CreateChild(&SharedDynamicStringMapTestBase::AddChild)); + test_env_->WaitForChildren(); + EXPECT_EQ(2, map->LookupElement(kExampleString1)); + EXPECT_EQ(2, map->LookupElement(kExampleString2)); + EXPECT_EQ(2, map->GetNumberInserted()); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::AddChild() { + scoped_ptr map(ChildInit()); + if ((map->IncrementElement(kExampleString1) == 0) || + (map->IncrementElement(kExampleString2) == 0)) { + test_env_->ChildFailed(); + } +} + +void SharedDynamicStringMapTestBase::TestAdd() { + scoped_ptr map(ParentInit()); + for (int i = 0; i < 2; i++) + ASSERT_TRUE(CreateChild(&SharedDynamicStringMapTestBase::AddChild)); + test_env_->WaitForChildren(); + EXPECT_EQ(2, map->LookupElement(kExampleString1)); + EXPECT_EQ(2, map->LookupElement(kExampleString2)); + EXPECT_EQ(2, map->GetNumberInserted()); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::TestQuarterFull() { + scoped_ptr map(ParentInit()); + ASSERT_TRUE(CreateFillChild(&SharedDynamicStringMapTestBase::AddFillChild, + 0, + kTableSize / 4)); + test_env_->WaitForChildren(); + EXPECT_EQ(kTableSize / 4, map->GetNumberInserted()); + GoogleString output; + StringWriter writer(&output); + map->Dump(&writer, &handler_); + // Dump outputs the table data in the form + // ": \n: \n: \n..." + // In this case all values should be 1 so for each of the (kTableSize / 4) + // strings there should be kStringSize characters plus a ":", " ", "1", and + // "\n" and minus a null character; hence (kTablsize / 4) * (kStringSize + 3) + EXPECT_EQ((kTableSize / 4) * (kStringSize + 3), output.length()); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::TestFillSingleThread() { + scoped_ptr map(ParentInit()); + EXPECT_EQ(0, map->GetNumberInserted()); + // One child fills the entire table. + ASSERT_TRUE(CreateFillChild(&SharedDynamicStringMapTestBase::AddFillChild, + 0, + kTableSize)); + test_env_->WaitForChildren(); + // Each entry should have been incremented once. + for (int i = 0; i < kTableSize; i++) + EXPECT_EQ(1, map->LookupElement(strings_[i])); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + // One child increments the entire table. + ASSERT_TRUE(CreateFillChild(&SharedDynamicStringMapTestBase::AddFillChild, + 0, + kTableSize)); + test_env_->WaitForChildren(); + // Each entry should have been incremented twice. + for (int i = 0; i < kTableSize; i++) + EXPECT_EQ(2, map->LookupElement(strings_[i])); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + // Once the table is full it should not accept additional strings. + ASSERT_TRUE(CreateChild(&SharedDynamicStringMapTestBase::AddToFullTable)); + test_env_->WaitForChildren(); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::TestFillMultipleNonOverlappingThreads() { + scoped_ptr map(ParentInit()); + CHECK_EQ(kTableSize % 4, 0); + // Each child will fill up 1/4 of the table. + for (int i = 0; i < 4; i++) + ASSERT_TRUE(CreateFillChild(&SharedDynamicStringMapTestBase::AddFillChild, + i * kTableSize / 4, + kTableSize / 4)); + test_env_->WaitForChildren(); + for (int i = 0; i < kTableSize; i++) + EXPECT_EQ(1, map->LookupElement(strings_[i])); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + // Once the table is full it should not accept additional strings. + ASSERT_TRUE(CreateChild(&SharedDynamicStringMapTestBase::AddToFullTable)); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + test_env_->WaitForChildren(); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::TestFillMultipleOverlappingThreads() { + scoped_ptr map(ParentInit()); + // Ensure that kTableSize is a multiple of 4. + CHECK_EQ(kTableSize & 3, 0); + // Each child will fill up 1/2 of the table - the table will get covered + // twice. + for (int i = 0; i < 4; i++) + ASSERT_TRUE(CreateFillChild(&SharedDynamicStringMapTestBase::AddFillChild, + i * kTableSize / 4, + kTableSize / 2)); + // In addition, the parent is going to fill up the entire table. + for (int i = 0; i < kTableSize; i++) + ASSERT_NE(0, map->IncrementElement(strings_[i])); + test_env_->WaitForChildren(); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + // Hence, we check that the values are equal to 3. + for (int i = 0; i < kTableSize; i++) + EXPECT_EQ(3, map->LookupElement(strings_[i])); + // Once the table is full it should not accept additional strings. + ASSERT_TRUE(CreateChild(&SharedDynamicStringMapTestBase::AddToFullTable)); + test_env_->WaitForChildren(); + EXPECT_EQ(kTableSize, map->GetNumberInserted()); + map->GlobalCleanup(&handler_); + EXPECT_EQ(0, handler_.SeriousMessages()); +} + +void SharedDynamicStringMapTestBase::AddFillChild(int start, + int number_of_strings) { + scoped_ptr map(ChildInit()); + for (int i = 0; i < number_of_strings; i++) { + if (0 == map->IncrementElement(strings_[(i + start) % kTableSize])) + test_env_->ChildFailed(); + } +} + +void SharedDynamicStringMapTestBase::AddToFullTable() { + scoped_ptr map(ChildInit()); + const char* string = strings_[kTableSize].c_str(); + EXPECT_EQ(0, map->IncrementElement(string)); +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,333 @@ +/* + * Copyright 2011 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: morlovich@google.com (Maksim Orlovich) +#include "net/instaweb/util/public/shared_mem_lock_manager.h" + +#include +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "net/instaweb/util/public/abstract_mutex.h" +#include "net/instaweb/util/public/abstract_shared_mem.h" +#include "net/instaweb/util/public/basictypes.h" +#include "net/instaweb/util/public/hasher.h" +#include "net/instaweb/util/public/message_handler.h" +#include "net/instaweb/util/public/string.h" +#include "net/instaweb/util/public/string_util.h" +#include "net/instaweb/util/public/scheduler.h" +#include "net/instaweb/util/public/scheduler_based_abstract_lock.h" +#include "net/instaweb/util/public/timer.h" + +namespace net_instaweb { + +namespace SharedMemLockData { + +// Memory structure: +// +// Bucket 0: +// Slot 0 +// lock name hash (64-bit) +// acquire timestamp (64-bit) +// Slot 1 +// ... +// Slot 15 +// Mutex +// (pad to 64-byte alignment) +// Bucket 1: +// .. +// Bucket 63: +// .. +// +// Each key is statically assigned to a bucket based on its hash. +// When we're trying to lock or unlock the given named lock, we lock +// the corresponding bucket. +// +// Whenever a lock is held, some slot in the corresponding bucket has its hash +// and the time of acquisition. When a slot is free (or unlocked), its timestamp +// is set to kNotAcquired. +// +// Very old locks can be stolen by new clients, in which case the timestamp gets +// updated. This serves multiple purposes: +// 1) It means only one extra process will grab it for each timeout period, +// as all others will see the new timestamp. +// 2) It makes it possible for the last grabber to be the one to unlock the +// lock, as we check the grabber's acquisition timestamp versus the lock's. +// +// A further issue is what happens when a bucket is overflowed. In that case, +// however, we simply state that lock acquisition failed. This is because the +// purpose of this service is to limit the load on the system, and the table +// getting filled suggests it's under heavy load as it is, in which case +// blocking further operations is desirable. +// +const size_t kBuckets = 64; // assumed to be <= 256 +const size_t kSlotsPerBucket = 32; + +struct Slot { + uint64 hash; + int64 acquired_at_ms; // kNotAcquired if free. +}; + +const int64 kNotAcquired = 0; + +struct Bucket { + Slot slots[kSlotsPerBucket]; + char mutex_base[1]; +}; + +inline size_t Align64(size_t in) { + return (in + 63) & ~63; +} + +inline size_t BucketSize(size_t lock_size) { + return Align64(offsetof(Bucket, mutex_base) + lock_size); +} + +inline size_t SegmentSize(size_t lock_size) { + return kBuckets * BucketSize(lock_size); +} + +} // namespace SharedMemLockData + +namespace Data = SharedMemLockData; + +class SharedMemLock : public SchedulerBasedAbstractLock { + public: + virtual ~SharedMemLock() { + Unlock(); + } + + virtual bool TryLock() { + return TryLockImpl(false, 0); + } + + virtual bool TryLockStealOld(int64 timeout_ms) { + return TryLockImpl(true, timeout_ms); + } + + virtual void Unlock() { + if (acquisition_time_ == Data::kNotAcquired) { + return; + } + + // Protect the bucket. + scoped_ptr lock(AttachMutex()); + ScopedMutex hold_lock(lock.get()); + + // Search for this lock. + // note: we permit empty slots in the middle, and start search at different + // positions depending on the hash to increase chance of quick hit. + // TODO(morlovich): Consider remembering which bucket we locked to avoid + // the search. (Could potentially be made lock-free, too). + size_t base = hash_ % Data::kSlotsPerBucket; + for (size_t offset = 0; offset < Data::kSlotsPerBucket; ++offset) { + size_t s = (base + offset) % Data::kSlotsPerBucket; + Data::Slot& slot = bucket_->slots[s]; + if (slot.hash == hash_ && slot.acquired_at_ms == acquisition_time_) { + slot.acquired_at_ms = Data::kNotAcquired; + break; + } + } + + acquisition_time_ = Data::kNotAcquired; + } + + virtual GoogleString name() { + return name_; + } + + virtual bool Held() { + return (acquisition_time_ != Data::kNotAcquired); + } + + protected: + virtual Scheduler* scheduler() const { + return manager_->scheduler_; + } + + private: + friend class SharedMemLockManager; + + // ctor should only be called by CreateNamedLock below. + SharedMemLock(SharedMemLockManager* manager, const StringPiece& name) + : manager_(manager), + name_(name.data(), name.size()), + acquisition_time_(Data::kNotAcquired) { + size_t bucket_num; + GetHashAndBucket(name_, &hash_, &bucket_num); + bucket_ = manager_->Bucket(bucket_num); + } + + // Compute hash and bucket used to store the lock for a given lock name. + void GetHashAndBucket(const StringPiece& name, uint64* hash_out, + size_t* bucket_out) { + GoogleString raw_hash = manager_->hasher_->RawHash(name); + + // We use separate hash bits to determine the hash and the bucket. + *bucket_out = static_cast(raw_hash[8]) % Data::kBuckets; + + uint64 hash = 0; + for (int c = 0; c < 8; ++c) { + hash = (hash << 8) | static_cast(raw_hash[c]); + } + *hash_out = hash; + } + + AbstractMutex* AttachMutex() const { + return manager_->seg_->AttachToSharedMutex( + manager_->MutexOffset(bucket_)); + } + + bool TryLockImpl(bool steal, int64 steal_timeout_ms) { + // Protect the bucket. + scoped_ptr lock(AttachMutex()); + ScopedMutex hold_lock(lock.get()); + + int64 now_ms = manager_->scheduler_->timer()->NowMs(); + if (now_ms == Data::kNotAcquired) { + ++now_ms; + } + + // Search for existing lock or empty slot. We need to check everything + // for existing lock, of course. + size_t empty_slot = Data::kSlotsPerBucket; + size_t base = hash_ % Data::kSlotsPerBucket; + for (size_t offset = 0; offset < Data::kSlotsPerBucket; ++offset) { + size_t s = (base + offset) % Data::kSlotsPerBucket; + Data::Slot& slot = bucket_->slots[s]; + if (slot.hash == hash_) { + if (slot.acquired_at_ms == Data::kNotAcquired || + (steal && ((now_ms - slot.acquired_at_ms) >= steal_timeout_ms))) { + // Stealing lock, or re-using a free slot we ourselves unlocked. + // + // We know we don't have an actual locked entry with our key elsewhere + // because: + // 1) After our last unlock of it no one else has ever locked it (or + // our key would have been overwritten), so if we ever performed an + // another lock operation we would have done it with this slot in + // present state. + // + // 2) We always chose the first candidate. + DoLockSlot(s, now_ms); + return true; + } else { + // Not permitted to steal or not stale enough to steal. + return false; + } + } else if (slot.acquired_at_ms == Data::kNotAcquired) { + if (empty_slot == Data::kSlotsPerBucket) { + empty_slot = s; + } + } + } + + if (empty_slot != Data::kSlotsPerBucket) { + DoLockSlot(empty_slot, now_ms); + return true; + } + + manager_->handler_->Message(kInfo, + "Overflowed bucket trying to grab lock."); + return false; + } + + // Writes out our ID and current timestamp into the slot, and marks the + // fact of our acquisition. + void DoLockSlot(size_t s, int64 now_ms) { + Data::Slot& slot = bucket_->slots[s]; + slot.hash = hash_; + slot.acquired_at_ms = now_ms; + acquisition_time_ = now_ms; + } + + SharedMemLockManager* manager_; + GoogleString name_; + + uint64 hash_; + + // Time at which we acquired the lock... + int64 acquisition_time_; + + // base pointer for the bucket we are in. + Data::Bucket* bucket_; + + DISALLOW_COPY_AND_ASSIGN(SharedMemLock); +}; + +SharedMemLockManager::SharedMemLockManager( + AbstractSharedMem* shm, const GoogleString& path, Scheduler* scheduler, + Hasher* hasher, MessageHandler* handler) + : shm_runtime_(shm), + path_(path), + scheduler_(scheduler), + hasher_(hasher), + handler_(handler), + lock_size_(shm->SharedMutexSize()) { + CHECK_GE(hasher_->RawHashSizeInBytes(), 9) << "Need >= 9 byte hashes"; +} + +SharedMemLockManager::~SharedMemLockManager() { +} + +bool SharedMemLockManager::Initialize() { + seg_.reset(shm_runtime_->CreateSegment(path_, Data::SegmentSize(lock_size_), + handler_)); + if (seg_.get() == NULL) { + handler_->Message(kError, "Unable to create memory segment for locks."); + return false; + } + + // Create the mutexes for each bucket + for (size_t bucket = 0; bucket < Data::kBuckets; ++bucket) { + if (!seg_->InitializeSharedMutex(MutexOffset(Bucket(bucket)), handler_)) { + handler_->Message(kError, "%s", + StrCat("Unable to create lock service mutex #", + Integer64ToString(bucket)).c_str()); + return false; + } + } + return true; +} + +bool SharedMemLockManager::Attach() { + size_t size = Data::SegmentSize(shm_runtime_->SharedMutexSize()); + seg_.reset(shm_runtime_->AttachToSegment(path_, size, handler_)); + if (seg_.get() == NULL) { + handler_->Message(kWarning, "Unable to attach to lock service SHM segment"); + return false; + } + + return true; +} + +void SharedMemLockManager::GlobalCleanup( + AbstractSharedMem* shm, const GoogleString& path, MessageHandler* handler) { + shm->DestroySegment(path, handler); +} + +NamedLock* SharedMemLockManager::CreateNamedLock(const StringPiece& name) { + return new SharedMemLock(this, name); +} + +Data::Bucket* SharedMemLockManager::Bucket(size_t bucket) { + return reinterpret_cast( + const_cast(seg_->Base()) + bucket * Data::BucketSize(lock_size_)); +} + +size_t SharedMemLockManager::MutexOffset(SharedMemLockData::Bucket* bucket) { + return &bucket->mutex_base[0] - seg_->Base(); +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager_test_base.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager_test_base.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager_test_base.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_lock_manager_test_base.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,187 @@ +// Copyright 2011 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: morlovich@google.com (Maksim Orlovich) + +#include "net/instaweb/util/public/shared_mem_lock_manager_test_base.h" + +#include "base/scoped_ptr.h" +#include "net/instaweb/util/public/function.h" +#include "net/instaweb/util/public/gtest.h" +#include "net/instaweb/util/public/md5_hasher.h" +#include "net/instaweb/util/public/mock_message_handler.h" +#include "net/instaweb/util/public/mock_timer.h" +#include "net/instaweb/util/public/named_lock_manager.h" +#include "net/instaweb/util/public/shared_mem_lock_manager.h" +#include "net/instaweb/util/public/shared_mem_test_base.h" +#include "net/instaweb/util/public/thread_system.h" + +namespace net_instaweb { + +namespace { + +const char kPath[] = "shm_locks"; +const char kLockA[] = "lock_a"; +const char kLockB[] = "lock_b"; + +} // namespace + +SharedMemLockManagerTestBase::SharedMemLockManagerTestBase( + SharedMemTestEnv* test_env) + : test_env_(test_env), + shmem_runtime_(test_env->CreateSharedMemRuntime()), + timer_(0), + thread_system_(ThreadSystem::CreateThreadSystem()), + scheduler_(thread_system_.get(), &timer_) { +} + +void SharedMemLockManagerTestBase::SetUp() { + root_lock_manager_.reset(CreateLockManager()); + EXPECT_TRUE(root_lock_manager_->Initialize()); +} + +void SharedMemLockManagerTestBase::TearDown() { + SharedMemLockManager::GlobalCleanup(shmem_runtime_.get(), kPath, &handler_); +} + +bool SharedMemLockManagerTestBase::CreateChild(TestMethod method) { + Function* callback = + new MemberFunction0(method, this); + return test_env_->CreateChild(callback); +} + +SharedMemLockManager* SharedMemLockManagerTestBase::CreateLockManager() { + return new SharedMemLockManager(shmem_runtime_.get(), kPath, &scheduler_, + &hasher_, &handler_); +} + +SharedMemLockManager* SharedMemLockManagerTestBase::AttachDefault() { + SharedMemLockManager* lock_man = CreateLockManager(); + if (!lock_man->Attach()) { + delete lock_man; + lock_man = NULL; + } + return lock_man; +} + +void SharedMemLockManagerTestBase::TestBasic() { + scoped_ptr lock_manager_(AttachDefault()); + ASSERT_TRUE(lock_manager_.get() != NULL); + scoped_ptr lock_a(lock_manager_->CreateNamedLock(kLockA)); + scoped_ptr lock_b(lock_manager_->CreateNamedLock(kLockB)); + + ASSERT_TRUE(lock_a.get() != NULL); + ASSERT_TRUE(lock_b.get() != NULL); + + EXPECT_FALSE(lock_a->Held()); + EXPECT_FALSE(lock_b->Held()); + + // Can lock exactly once... + EXPECT_TRUE(lock_a->TryLock()); + EXPECT_TRUE(lock_b->TryLock()); + EXPECT_TRUE(lock_a->Held()); + EXPECT_TRUE(lock_b->Held()); + EXPECT_FALSE(lock_a->TryLock()); + EXPECT_FALSE(lock_b->TryLock()); + EXPECT_TRUE(lock_a->Held()); + EXPECT_TRUE(lock_b->Held()); + + // Unlocking lets one lock again + lock_b->Unlock(); + EXPECT_FALSE(lock_b->Held()); + EXPECT_FALSE(lock_a->TryLock()); + EXPECT_TRUE(lock_b->TryLock()); + + // Now unlock A, and let kid confirm the state + lock_a->Unlock(); + EXPECT_FALSE(lock_a->Held()); + CreateChild(&SharedMemLockManagerTestBase::TestBasicChild); + test_env_->WaitForChildren(); + + // A should still be unlocked since child's locks should get cleaned up + // by ~NamedLock.. but not lock b, which we were holding + EXPECT_TRUE(lock_a->TryLock()); + EXPECT_FALSE(lock_b->TryLock()); +} + +void SharedMemLockManagerTestBase::TestBasicChild() { + scoped_ptr lock_manager_(AttachDefault()); + scoped_ptr lock_a(lock_manager_->CreateNamedLock(kLockA)); + scoped_ptr lock_b(lock_manager_->CreateNamedLock(kLockB)); + + if (lock_a.get() == NULL || lock_b.get() == NULL) { + test_env_->ChildFailed(); + } + + // A should lock fine + if (!lock_a->TryLock() || !lock_a->Held()) { + test_env_->ChildFailed(); + } + + // B shouldn't lock fine. + if (lock_b->TryLock() || lock_b->Held()) { + test_env_->ChildFailed(); + } + + // Note: here we should unlock a due to destruction of A. +} + +void SharedMemLockManagerTestBase::TestDestructorUnlock() { + // Standalone test for destructors cleaning up. It is covered by the + // above, but this does it single-threaded, without weird things. + scoped_ptr lock_manager_(AttachDefault()); + ASSERT_TRUE(lock_manager_.get() != NULL); + + { + scoped_ptr lock_a(lock_manager_->CreateNamedLock(kLockA)); + EXPECT_TRUE(lock_a->TryLock()); + } + + { + scoped_ptr lock_a(lock_manager_->CreateNamedLock(kLockA)); + EXPECT_TRUE(lock_a->TryLock()); + } +} + +void SharedMemLockManagerTestBase::TestSteal() { + scoped_ptr lock_manager_(AttachDefault()); + ASSERT_TRUE(lock_manager_.get() != NULL); + scoped_ptr lock_a(lock_manager_->CreateNamedLock(kLockA)); + EXPECT_TRUE(lock_a->TryLock()); + EXPECT_TRUE(lock_a->Held()); + CreateChild(&SharedMemLockManagerTestBase::TestStealChild); + test_env_->WaitForChildren(); +} + +void SharedMemLockManagerTestBase::TestStealChild() { + const int kStealTimeMs = 1000; + + scoped_ptr lock_manager_(AttachDefault()); + ASSERT_TRUE(lock_manager_.get() != NULL); + scoped_ptr lock_a(lock_manager_->CreateNamedLock(kLockA)); + + // First, attempting to steal should fail, as 'time' hasn't moved yet. + if (lock_a->TryLockStealOld(kStealTimeMs) || lock_a->Held()) { + test_env_->ChildFailed(); + } + + timer_.AdvanceMs(kStealTimeMs + 1); + + // Now it should succeed. + if (!lock_a->TryLockStealOld(kStealTimeMs) || !lock_a->Held()) { + test_env_->ChildFailed(); + } +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,307 @@ +/* + * Copyright 2011 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: jhoch@google.com (Jason Hoch) + +#include "net/instaweb/util/public/shared_mem_referer_statistics.h" + +#include +#include +#include "net/instaweb/util/public/google_url.h" +#include "net/instaweb/util/public/query_params.h" +#include "net/instaweb/util/public/shared_dynamic_string_map.h" +#include "net/instaweb/util/public/writer.h" + +namespace net_instaweb { + +// We don't want this to conflict with another query parameter name, and length +// also matters (shorter is better). I picked this somewhat arbitrarily... +const char SharedMemRefererStatistics::kParamName[] = "div_location"; + +namespace { + +// The encoding scheme for referrals is the following: +// + + + +// where separator string is kSeparatorString and type string is either +// kPageString, kDivLocationString, or kResourceString, depending on the type +// of the target. +// The type string is used to differentiate different types of targets at the +// time of decoding, while the separator string is used to make the information +// parseable. Therefore the separator string has to be distinguishable from a +// URL (e.g. a space character, since there are no spaces in URLs). +const char kSeparatorString[] = " "; +const char kPageString[] = "p"; +const char kDivLocationString[] = "d"; +const char kResourceString[] = "r"; + +} // namespace + +SharedMemRefererStatistics::SharedMemRefererStatistics( + size_t number_of_strings, + size_t average_string_length, + AbstractSharedMem* shm_runtime, + const GoogleString& filename_prefix, + const GoogleString& filename_suffix) + : shared_dynamic_string_map_(new SharedDynamicStringMap( + number_of_strings, + average_string_length, + shm_runtime, + filename_prefix, + filename_suffix)) { +} + +SharedMemRefererStatistics::~SharedMemRefererStatistics() {} + +bool SharedMemRefererStatistics::InitSegment(bool parent, + MessageHandler* message_handler) { + return shared_dynamic_string_map_->InitSegment(parent, message_handler); +} + +void SharedMemRefererStatistics::LogPageRequestWithoutReferer( + const GoogleUrl& target) { + // We don't need to use target_string later, but we need a placeholder + // variable. We still use LogPageRequest(target, target_string) so we + // don't duplicate code with LogReferedPageRequest + GoogleString placeholder; + LogPageRequest(target, &placeholder); +} + +void SharedMemRefererStatistics::LogPageRequestWithReferer( + const GoogleUrl& target, + const GoogleUrl& referer) { + GoogleString target_string; + LogPageRequest(target, &target_string); + GoogleString referer_string = GetUrlEntryStringForUrl(referer); + GoogleString reference_entry = GetEntryForReferedPage(target_string, + referer_string); + shared_dynamic_string_map_->IncrementElement(reference_entry); + GoogleString div_location = GetDivLocationEntryStringForUrl(target); + if (!div_location.empty()) { + GoogleString div_location_entry = + GetEntryForReferedDivLocation(div_location, referer_string); + shared_dynamic_string_map_->IncrementElement(div_location_entry); + } +} + +void SharedMemRefererStatistics::LogResourceRequestWithReferer( + const GoogleUrl& target, + const GoogleUrl& referer) { + GoogleString entry = GetEntryForReferedResource( + GetUrlEntryStringForUrl(target), + GetUrlEntryStringForUrl(referer)); + shared_dynamic_string_map_->IncrementElement(entry); +} + +// We want to avoid duplicating code between LogReferedPageRequest and +// LogVisitedPageRequest, but we also don't want to perform GetEntryStringForUrl +// twice. +void SharedMemRefererStatistics::LogPageRequest( + const GoogleUrl& target, + GoogleString* target_string) { + *target_string = GetUrlEntryStringForUrl(target); + GoogleString visit_entry = GetEntryForVisitedPage(*target_string); + shared_dynamic_string_map_->IncrementElement(visit_entry); +} + +int SharedMemRefererStatistics::GetNumberOfVisitsForUrl(const GoogleUrl& url) { + GoogleString entry = GetEntryForVisitedPage(GetUrlEntryStringForUrl(url)); + return shared_dynamic_string_map_->LookupElement(entry); +} + +int SharedMemRefererStatistics::GetNumberOfReferencesFromUrlToPage( + const GoogleUrl& from_url, + const GoogleUrl& to_url) { + GoogleString entry = GetEntryForReferedPage( + GetUrlEntryStringForUrl(to_url), + GetUrlEntryStringForUrl(from_url)); + return shared_dynamic_string_map_->LookupElement(entry); +} + +int SharedMemRefererStatistics::GetNumberOfReferencesFromUrlToDivLocation( + const GoogleUrl& from_url, + const GoogleString& div_location) { + GoogleString entry = GetEntryForReferedDivLocation( + GetEntryStringForDivLocation(div_location), + GetUrlEntryStringForUrl(from_url)); + return shared_dynamic_string_map_->LookupElement(entry); +} + +int SharedMemRefererStatistics::GetNumberOfReferencesFromUrlToResource( + const GoogleUrl& from_url, + const GoogleUrl& resource_url) { + GoogleString entry = GetEntryForReferedResource( + GetUrlEntryStringForUrl(resource_url), + GetUrlEntryStringForUrl(from_url)); + return shared_dynamic_string_map_->LookupElement(entry); +} + +GoogleString SharedMemRefererStatistics::GetDivLocationFromUrl( + const GoogleUrl& url) { + QueryParams query_params; + query_params.Parse(url.Query()); + ConstStringStarVector div_locations; + if ((query_params.Lookup(kParamName, &div_locations)) && + (!div_locations.empty())) { + return *div_locations[0]; + } + return ""; +} + +GoogleString SharedMemRefererStatistics::GetEntryStringForUrlString( + const StringPiece& url_string) const { + // Default implementation does nothing + return url_string.as_string(); +} + +GoogleString SharedMemRefererStatistics::GetEntryStringForDivLocation( + const StringPiece& div_location) const { + // Default implementation does nothing + return div_location.as_string(); +} + +GoogleString SharedMemRefererStatistics::GetUrlEntryStringForUrl( + const GoogleUrl& url) const { + return GetEntryStringForUrlString(url.AllExceptQuery()); +} + +GoogleString SharedMemRefererStatistics::GetDivLocationEntryStringForUrl( + const GoogleUrl& url) const { + return GetEntryStringForDivLocation(GetDivLocationFromUrl(url)); +} + +GoogleString SharedMemRefererStatistics::GetEntryForReferedPage( + const StringPiece& target, + const StringPiece& referer) { + return StrCat(target, kSeparatorString, kPageString, referer); +} + +GoogleString SharedMemRefererStatistics::GetEntryForReferedDivLocation( + const StringPiece& target, + const StringPiece& referer) { + return StrCat(target, kSeparatorString, kDivLocationString, referer); +} + +GoogleString SharedMemRefererStatistics::GetEntryForVisitedPage( + const StringPiece& target) { + return target.as_string(); +} + +GoogleString SharedMemRefererStatistics::GetEntryForReferedResource( + const StringPiece& target, + const StringPiece& referer) { + return StrCat(target, kSeparatorString, kResourceString, referer); +} + +GoogleString SharedMemRefererStatistics::DecodeEntry( + const StringPiece& entry, + GoogleString* target, + GoogleString* referer) const { + size_t separator_pos = entry.find(kSeparatorString); + if (separator_pos == StringPiece::npos) { + *target = entry.as_string(); + *referer = ""; + return StrCat(*target, " visits: "); + } else { + StringPiece basic_target(entry.data(), separator_pos); + *referer = StringPiece(entry.data() + separator_pos + 2).as_string(); + char type_char = *(entry.data() + separator_pos + 1); + const char* type_string = ""; + if (type_char == kPageString[0]) { + type_string = "page "; + } else if (type_char == kDivLocationString[0]) { + type_string = "div location "; + } else if (type_char == kResourceString[0]) { + type_string = "resource "; + } + *target = StrCat(type_string, basic_target, " : "); + return StrCat(*referer, " refered ", *target); + } +} + +GoogleString SharedMemRefererStatistics::DecodeEntry( + const StringPiece& entry) const { + GoogleString target; + GoogleString referer; + return DecodeEntry(entry, &target, &referer); +} + +void SharedMemRefererStatistics::GlobalCleanup( + MessageHandler* message_handler) { + shared_dynamic_string_map_->GlobalCleanup(message_handler); +} + +void SharedMemRefererStatistics::DumpFast(Writer* writer, + MessageHandler* message_handler) { + shared_dynamic_string_map_->Dump(writer, message_handler); +} + +void SharedMemRefererStatistics::DumpSimple(Writer* writer, + MessageHandler* message_handler) { + StringSet strings; + shared_dynamic_string_map_->GetKeys(&strings); + for (StringSet::iterator i = strings.begin(); i != strings.end(); i++) { + GoogleString string = *i; + int value = shared_dynamic_string_map_->LookupElement(string); + writer->Write(DecodeEntry(string), message_handler); + writer->Write(IntegerToString(value), message_handler); + writer->Write("\n", message_handler); + } +} + +void SharedMemRefererStatistics::DumpOrganized( + Writer* writer, + MessageHandler* message_handler) { + StringSet strings; + shared_dynamic_string_map_->GetKeys(&strings); + // We first accumulate referers and group referrals by referer + StringSet referers; + std::map referees_by_referer; + StringStringMap visits_by_referer; + for (StringSet::iterator i = strings.begin(); i != strings.end(); i++) { + GoogleString string = *i; + int value = shared_dynamic_string_map_->LookupElement(string); + GoogleString target; + GoogleString referer; + GoogleString output = DecodeEntry(string, &target, &referer); + if (referer.empty()) { + visits_by_referer[target] = StrCat(output, IntegerToString(value)); + referers.insert(target); + } else { + referees_by_referer[referer].insert( + StrCat(target, IntegerToString(value))); + referers.insert(referer); + } + } + // We now dump the grouped referals in a nice readable format + for (StringSet::iterator i = referers.begin(); i != referers.end(); i++) { + GoogleString referer = *i; + writer->Write(visits_by_referer[referer], message_handler); + writer->Write("\n", message_handler); + StringSet referees = referees_by_referer[referer]; + if (referees.size() > 0) { + writer->Write(referer, message_handler); + writer->Write(" refered:\n", message_handler); + for (StringSet::iterator j = referees.begin(); j != referees.end(); j++) { + GoogleString referee = *j; + writer->Write(" ", message_handler); + writer->Write(referee, message_handler); + writer->Write("\n", message_handler); + } + } + } +} + +} // namespace net_instaweb Added: httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics_test_base.cc URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics_test_base.cc?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics_test_base.cc (added) +++ httpd/mod_spdy/trunk/net/instaweb/util/shared_mem_referer_statistics_test_base.cc Thu May 1 11:43:36 2014 @@ -0,0 +1,380 @@ +/* + * Copyright 2011 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: jhoch@google.com (Jason Hoch) + +#include "net/instaweb/util/public/shared_mem_referer_statistics_test_base.h" + +#include "net/instaweb/util/public/function.h" +#include "net/instaweb/util/public/google_url.h" +#include "net/instaweb/util/public/shared_mem_referer_statistics.h" +#include "net/instaweb/util/public/string_util.h" +#include "net/instaweb/util/public/string_writer.h" + +namespace net_instaweb { + +const int SharedMemRefererStatisticsTestBase::kNumberOfStrings = 1024; +const int SharedMemRefererStatisticsTestBase::kStringSize = 64; +const char SharedMemRefererStatisticsTestBase::kPrefix[] = "/prefix/"; +const char SharedMemRefererStatisticsTestBase::kSuffix[] = "suffix"; + +namespace { + +// kEmptyUrl is used to convey a break in referrals to LogSequenceOfPageRequests +const GoogleUrl kEmptyUrl(""); +const GoogleString kBase("http://www.example.com/"); + +const TestUrl kNews(kBase + GoogleString("news"), + GoogleString("")); +const TestUrl kUSNews(kBase + GoogleString("news/us"), + GoogleString("1.1.0.1")); +const TestUrl kUSNewsArticle(kBase + GoogleString("news/us/article"), + GoogleString("1.1.2.0")); +const TestUrl kUSNewsArticleImage( + kBase + GoogleString("images/news_us_article.jpg"), + GoogleString("")); +const TestUrl kNewUSNewsArticle(kBase + GoogleString("news/us/article2"), + GoogleString("1.1.2.0")); +const TestUrl kNewOldUSNewsArticle(kBase + GoogleString("news/us/article"), + GoogleString("1.1.2.1.0")); +const TestUrl kAccount(kBase + GoogleString("account"), + GoogleString("0.0.9")); +const TestUrl kProfile(kBase + GoogleString("account/profile.html"), + GoogleString("1.3.0"), + GoogleString("user=jason")); +const TestUrl kOtherProfile(kBase + GoogleString("account/profile.html"), + GoogleString("1.3.0"), + GoogleString("user=jhoch")); + +} // namespace + +GoogleString TestUrl::FormUrl(GoogleString input_string, + GoogleString input_div_location, + GoogleString query_params) { + if (input_div_location.empty()) { + if (query_params.empty()) { + return input_string; + } else { + return StrCat(input_string, "?", query_params); + } + } else { + if (query_params.empty()) { + return StrCat(input_string, "?div_location=", input_div_location); + } else { + return StrCat(input_string, "?div_location=", input_div_location, + "&", query_params); + } + } +} + +SharedMemRefererStatisticsTestBase::SharedMemRefererStatisticsTestBase( + SharedMemTestEnv* test_env) + : test_env_(test_env), + shmem_runtime_(test_env->CreateSharedMemRuntime()) { +} + +bool SharedMemRefererStatisticsTestBase::CreateChild(TestMethod method) { + Function* callback = + new MemberFunction0(method, this); + return test_env_->CreateChild(callback); +} + +SharedMemRefererStatistics* SharedMemRefererStatisticsTestBase::ChildInit() { + SharedMemRefererStatistics* stats = new SharedMemRefererStatistics( + kNumberOfStrings, + kStringSize, + shmem_runtime_.get(), + kPrefix, + kSuffix); + stats->InitSegment(false, &message_handler_); + return stats; +} + +SharedMemRefererStatistics* SharedMemRefererStatisticsTestBase::ParentInit() { + SharedMemRefererStatistics* stats = new SharedMemRefererStatistics( + kNumberOfStrings, + kStringSize, + shmem_runtime_.get(), + kPrefix, + kSuffix); + stats->InitSegment(true, &message_handler_); + return stats; +} + +void SharedMemRefererStatisticsTestBase::LogSequenceOfPageRequests( + SharedMemRefererStatistics* stats, + const GoogleUrl* urls[], + int number_of_urls) { + bool previous_was_null = true; + for (int i = 0; i < number_of_urls; i++) { + // kEmptyUrl("") is used to signify break in referrals + if (urls[i]->UncheckedSpec().empty()) { + previous_was_null = true; + } else { + if (previous_was_null) { + stats->LogPageRequestWithoutReferer(*urls[i]); + } else { + stats->LogPageRequestWithReferer(*urls[i], *urls[i - 1]); + } + previous_was_null = false; + } + } +} + +void SharedMemRefererStatisticsTestBase::TestGetDivLocationFromUrl() { + scoped_ptr stats(ParentInit()); + const char value[] = "0.0.0"; + GoogleString url = GoogleString("http://a.com/?") + + SharedMemRefererStatistics::kParamName + + GoogleString("=") + value; + GoogleUrl test_url(url); + EXPECT_EQ(value, stats->GetDivLocationFromUrl(test_url)); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestSimple() { + scoped_ptr stats(ParentInit()); + EXPECT_EQ(0, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(0, stats->GetNumberOfVisitsForUrl(kUSNews.url)); + stats->LogPageRequestWithoutReferer(kNews.url); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(0, stats->GetNumberOfVisitsForUrl(kUSNews.url)); + stats->LogPageRequestWithReferer(kUSNews.url, kNews.url); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kUSNews.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage(kNews.url, + kUSNews.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kNews.url, kUSNews.div_location)); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestResource() { + scoped_ptr stats(ParentInit()); + const GoogleUrl* urls[] = {&kNews.url, &kUSNews.url, &kUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + stats->LogResourceRequestWithReferer(kUSNewsArticleImage.url, + kUSNewsArticle.url); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kUSNews.url)); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage(kNews.url, + kUSNews.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage(kUSNews.url, + kUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kNews.url, kUSNews.div_location)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kUSNewsArticle.div_location)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToResource( + kUSNewsArticle.url, kUSNewsArticleImage.url)); + EXPECT_EQ(0, stats->GetNumberOfVisitsForUrl(kUSNewsArticleImage.url)); + EXPECT_EQ(0, stats->GetNumberOfReferencesFromUrlToPage( + kUSNewsArticle.url, kUSNewsArticleImage.url)); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestIgnoreQueryParams() { + scoped_ptr stats(ParentInit()); + const GoogleUrl* urls[] = {&kNews.url, &kAccount.url, &kProfile.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + stats->LogPageRequestWithReferer(kOtherProfile.url, kAccount.url); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kAccount.url)); + EXPECT_EQ(2, stats->GetNumberOfVisitsForUrl(kProfile.url)); + EXPECT_EQ(2, stats->GetNumberOfVisitsForUrl(kOtherProfile.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage(kNews.url, + kAccount.url)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToPage(kAccount.url, + kProfile.url)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToPage(kAccount.url, + kOtherProfile.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kNews.url, kAccount.div_location)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToDivLocation( + kAccount.url, kProfile.div_location)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToDivLocation( + kAccount.url, kOtherProfile.div_location)); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestDivLocation() { + scoped_ptr stats(ParentInit()); + const GoogleUrl* urls[] = {&kNews.url, &kUSNews.url, &kUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + stats->LogPageRequestWithReferer(kNewUSNewsArticle.url, kUSNews.url); + stats->LogPageRequestWithReferer(kNewOldUSNewsArticle.url, kUSNews.url); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kUSNews.url)); + EXPECT_EQ(2, stats->GetNumberOfVisitsForUrl(kUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfVisitsForUrl(kNewUSNewsArticle.url)); + EXPECT_EQ(2, stats->GetNumberOfVisitsForUrl(kNewOldUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage(kNews.url, + kUSNews.url)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToPage(kUSNews.url, + kUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage( + kUSNews.url, kNewUSNewsArticle.url)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToPage( + kUSNews.url, kNewOldUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kNews.url, kUSNews.div_location)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kUSNewsArticle.div_location)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kNewUSNewsArticle.div_location)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kNewOldUSNewsArticle.div_location)); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestDumpFast() { + scoped_ptr stats(ParentInit()); + const GoogleUrl* urls[] = {&kNews.url, &kUSNews.url, &kUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + stats->LogResourceRequestWithReferer(kUSNewsArticleImage.url, + kUSNewsArticle.url); + GoogleString expected_dump = + kNews.string + ": 1\n" + + kUSNews.string + ": 1\n" + + kUSNews.string + " p" + kNews.string + ": 1\n" + + kUSNews.div_location + " d" + kNews.string + ": 1\n" + + kUSNewsArticle.string + ": 1\n" + + kUSNewsArticle.string + " p" + kUSNews.string + ": 1\n" + + kUSNewsArticle.div_location + " d" + kUSNews.string + ": 1\n" + + kUSNewsArticleImage.string + " r" + kUSNewsArticle.string + ": 1\n"; + GoogleString string; + StringWriter writer(&string); + stats->DumpFast(&writer, &message_handler_); + EXPECT_EQ(expected_dump, string); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestDumpSimple() { + scoped_ptr stats(ParentInit()); + const GoogleUrl* urls[] = {&kNews.url, &kUSNews.url, &kUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + stats->LogResourceRequestWithReferer(kUSNewsArticleImage.url, + kUSNewsArticle.url); + GoogleString expected_dump = + kNews.string + " refered div location " + + kUSNews.div_location + " : 1\n" + + kUSNews.string + " refered div location " + + kUSNewsArticle.div_location + " : 1\n" + + kUSNewsArticle.string + " refered resource " + + kUSNewsArticleImage.string + " : 1\n" + + kNews.string + " visits: 1\n" + + kUSNews.string + " visits: 1\n" + + kNews.string + " refered page " + kUSNews.string + " : 1\n" + + kUSNewsArticle.string + " visits: 1\n" + + kUSNews.string + " refered page " + kUSNewsArticle.string + " : 1\n"; + GoogleString string; + StringWriter writer(&string); + stats->DumpSimple(&writer, &message_handler_); + EXPECT_EQ(expected_dump, string); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestDumpOrganized() { + scoped_ptr stats(ParentInit()); + const GoogleUrl* urls[] = {&kNews.url, &kUSNews.url, &kUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + stats->LogResourceRequestWithReferer(kUSNewsArticleImage.url, + kUSNewsArticle.url); + GoogleString expected_dump = + kNews.string + " visits: 1\n" + + kNews.string + " refered:\n" + + " div location " + kUSNews.div_location + " : 1\n" + + " page " + kUSNews.string + " : 1\n" + + kUSNews.string + " visits: 1\n" + + kUSNews.string + " refered:\n" + + " div location " + kUSNewsArticle.div_location + " : 1\n" + + " page " + kUSNewsArticle.string + " : 1\n" + + kUSNewsArticle.string + " visits: 1\n" + + kUSNewsArticle.string + " refered:\n" + + " resource " + kUSNewsArticleImage.string + " : 1\n"; + GoogleString string; + StringWriter writer(&string); + stats->DumpOrganized(&writer, &message_handler_); + EXPECT_EQ(expected_dump, string); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::TestMultiProcess() { + scoped_ptr stats(ParentInit()); + for (int i = 0; i < 2; i++) + ASSERT_TRUE(CreateChild(&SharedMemRefererStatisticsTestBase::AddChild)); + const GoogleUrl* urls[] = {&kNews.url, &kAccount.url, &kProfile.url, + &kEmptyUrl, &kNews.url, &kUSNews.url, + &kNewOldUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); + test_env_->WaitForChildren(); + EXPECT_EQ(6, stats->GetNumberOfVisitsForUrl(kNews.url)); + EXPECT_EQ(5, stats->GetNumberOfVisitsForUrl(kUSNews.url)); + EXPECT_EQ(3, stats->GetNumberOfVisitsForUrl(kUSNewsArticle.url)); + EXPECT_EQ(2, stats->GetNumberOfVisitsForUrl(kNewUSNewsArticle.url)); + EXPECT_EQ(3, stats->GetNumberOfVisitsForUrl(kNewOldUSNewsArticle.url)); + EXPECT_EQ(3, stats->GetNumberOfVisitsForUrl(kAccount.url)); + EXPECT_EQ(3, stats->GetNumberOfVisitsForUrl(kProfile.url)); + EXPECT_EQ(5, stats->GetNumberOfReferencesFromUrlToPage(kNews.url, + kUSNews.url)); + EXPECT_EQ(3, stats->GetNumberOfReferencesFromUrlToPage(kUSNews.url, + kUSNewsArticle.url)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToPage(kNews.url, + kAccount.url)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToPage(kUSNewsArticle.url, + kAccount.url)); + EXPECT_EQ(3, stats->GetNumberOfReferencesFromUrlToPage(kAccount.url, + kProfile.url)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToPage( + kUSNews.url, kNewUSNewsArticle.url)); + EXPECT_EQ(3, stats->GetNumberOfReferencesFromUrlToPage( + kUSNews.url, kNewOldUSNewsArticle.url)); + EXPECT_EQ(5, stats->GetNumberOfReferencesFromUrlToDivLocation( + kNews.url, kUSNews.div_location)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kNews.url, kAccount.div_location)); + EXPECT_EQ(4, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kUSNewsArticle.div_location)); + EXPECT_EQ(2, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNewsArticle.url, kAccount.div_location)); + EXPECT_EQ(3, stats->GetNumberOfReferencesFromUrlToDivLocation( + kAccount.url, kProfile.div_location)); + EXPECT_EQ(4, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kNewUSNewsArticle.div_location)); + EXPECT_EQ(1, stats->GetNumberOfReferencesFromUrlToDivLocation( + kUSNews.url, kNewOldUSNewsArticle.div_location)); + stats->GlobalCleanup(&message_handler_); + EXPECT_EQ(0, message_handler_.SeriousMessages()); +} + +void SharedMemRefererStatisticsTestBase::AddChild() { + scoped_ptr stats(ChildInit()); + const GoogleUrl* urls[] = {&kNews.url, &kUSNews.url, &kUSNewsArticle.url, + &kAccount.url, &kProfile.url, &kEmptyUrl, + &kNews.url, &kUSNews.url, &kNewUSNewsArticle.url}; + LogSequenceOfPageRequests(stats.get(), urls, arraysize(urls)); +} + +} // namespace net_instaweb