/* * Any change of global outstanding allocation balance should come * from the combination of active memory account balance increase * and any shared header allocation (SharedChunkHeadersMemoryAccount * balance increase) */ void test__MemoryAccounting_Allocate__ChargesOnlyActiveAccount(void **state) { MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account other than Rollover */ MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount); assert_true(ActiveMemoryAccount == newActiveAccount); uint64 prevOutstanding = MemoryAccountingOutstandingBalance; uint64 prevSharedHeaderAlloc = SharedChunkHeadersMemoryAccount->allocated; void *testAlloc = palloc(NEW_ALLOC_SIZE); /* * Any change of outstanding balance is coming from new allocation * and the associated shared header allocation */ assert_true((newActiveAccount->allocated - newActiveAccount->freed) + (SharedChunkHeadersMemoryAccount->allocated - prevSharedHeaderAlloc) == (MemoryAccountingOutstandingBalance - prevOutstanding)); /* * We need to pfree as we have allocated this memory * in the TopMemoryContext with a short-living memory account */ pfree(testAlloc); }
/* Tests whether a large allocation updates balance of the active memory account */ void test__AllocSetAllocImpl__LargeAllocInActiveMemoryAccount(void **state) { MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account other than Rollover */ MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount); uint64 prevOutstanding = MemoryAccountingOutstandingBalance; uint64 prevSharedHeaderAlloc = SharedChunkHeadersMemoryAccount->allocated; uint64 prevActiveAlloc = ActiveMemoryAccount->allocated; int chunkSize = ALLOC_CHUNK_LIMIT + 1; /* This chunk should record newAccount as the owner */ void *testAlloc = palloc(chunkSize); /* * All the new allocation should go to ActiveMemoryAccount, and the * SharedChunkHeadersMemoryAccount should contribute to add up to * the outstanding balance change */ assert_true((newActiveAccount->allocated >= prevActiveAlloc + chunkSize) && (SharedChunkHeadersMemoryAccount->allocated > prevSharedHeaderAlloc) && (newActiveAccount->allocated - newActiveAccount->freed) + (SharedChunkHeadersMemoryAccount->allocated - prevSharedHeaderAlloc) == (MemoryAccountingOutstandingBalance - prevOutstanding)); /* * We need to pfree as we have allocated this memory * in the TopMemoryContext with a short-living memory account */ pfree(testAlloc); }
/* * serializeNode - * This is used on the query dispatcher to serialize Plan and Query Trees for * dispatching to qExecs. * The returned string is palloc'ed in the current memory context. */ char * serializeNode(Node *node, int *size, int *uncompressed_size_out) { char *pszNode; char *sNode; int uncompressed_size; Assert(node != NULL); Assert(size != NULL); START_MEMORY_ACCOUNT(MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Serializer)); { pszNode = nodeToBinaryStringFast(node, &uncompressed_size); Assert(pszNode != NULL); if (NULL != uncompressed_size_out) { *uncompressed_size_out = uncompressed_size; } sNode = compress_string(pszNode, uncompressed_size, size); pfree(pszNode); if (DEBUG5 >= log_min_messages) { Node * newnode = NULL; PG_TRY(); { newnode = deserializeNode(sNode, *size); } PG_CATCH(); { elog_node_display(DEBUG5, "Before serialization", node, true); PG_RE_THROW(); } PG_END_TRY(); /* Some plans guarantee these differences (see serialization * of plan nodes -- they avoid sending QD-only info out) */ if (strcmp(nodeToString(node), nodeToString(newnode)) != 0) { elog_node_display(DEBUG5, "Before serialization", node, true); elog_node_display(DEBUG5, "After deserialization", newnode, true); } } } END_MEMORY_ACCOUNT(); return sNode; }
/* Tests whether a free operation decreases balance of only the owning account */ void test__AllocFreeInfo__FreesOnlyOwnerAccount(void **state) { MemoryAccount *newAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newAccount); /* This chunk should record newAccount as the owner */ void *testAlloc = palloc(NEW_ALLOC_SIZE); MemoryAccounting_SwitchAccount(oldActiveAccount); assert_true(ActiveMemoryAccount != newAccount); uint64 originalActiveBalance = ActiveMemoryAccount->allocated - ActiveMemoryAccount->freed; uint64 newAccountBalance = newAccount->allocated - newAccount->freed; uint64 newAccountFreed = newAccount->freed; uint64 sharedFreed = SharedChunkHeadersMemoryAccount->freed; uint64 prevOutstanding = MemoryAccountingOutstandingBalance; pfree(testAlloc); /* * Make sure that the active account is unchanged while the owner account * balance was reduced */ assert_true(ActiveMemoryAccount->allocated - ActiveMemoryAccount->freed == originalActiveBalance); /* Balance was released from newAccount */ assert_true(newAccount->allocated - newAccount->freed <= newAccountBalance - NEW_ALLOC_SIZE); /* The shared header should be released */ assert_true(SharedChunkHeadersMemoryAccount->freed - sharedFreed > 0); /* All the difference in outstanding balance should come from newAccount * (which was the owner of the chunk) balance change and the resulting * shared header release */ assert_true(prevOutstanding - MemoryAccountingOutstandingBalance == newAccount->freed - newAccountFreed + SharedChunkHeadersMemoryAccount->freed - sharedFreed); }
/* * Tests whether we charge shared chunk header account during an allocation * when header sharing is not possible */ void test__AllocAllocInfo__ChargesSharedChunkHeadersMemoryAccount(void **state) { MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account to force a new shared header allocation */ MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount); uint64 prevSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed; void *testAlloc = palloc(NEW_ALLOC_SIZE); uint64 newSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed; /* * A new sharedHeader should record the associated memory overhead * in the SharedChunkHeadersMemoryAccount */ assert_true(prevSharedBalance < newSharedBalance); pfree(testAlloc); }
/* Tests whether we are promptly freeing shared header that is no longer shared */ void test__AllocFreeInfo__FreesObsoleteHeader(void **state) { MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account to force a new shared header allocation */ MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount); uint64 prevSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed; void *testAlloc = palloc(NEW_ALLOC_SIZE); uint64 newSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed; assert_true(prevSharedBalance < newSharedBalance); StandardChunkHeader *header = (StandardChunkHeader *) ((char *) testAlloc - STANDARDCHUNKHEADERSIZE); AllocSet set = (AllocSet)CurrentMemoryContext; SharedChunkHeader *headerPointer = header->sharedHeader; assert_true(set->sharedHeaderList == headerPointer); /* * As no one else is using the sharedHeader of testAlloc, * we should release upon pfree of testAlloc */ pfree(testAlloc); /* * The sharedHeaderList should no longer store the sharedHeader * that we have just released. Note: the headerPointer is now * and invalid pointer as we have already freed the shared header * memory */ assert_true(set->sharedHeaderList != headerPointer); }
/* * deserializeNode - * This is used on the qExecs to deserialize serialized Plan and Query Trees * received from the dispatcher. * The returned node is palloc'ed in the current memory context. */ Node * deserializeNode(const char *strNode, int size) { char *sNode; Node *node; int uncompressed_len; Assert(strNode != NULL); START_MEMORY_ACCOUNT(MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Deserializer)); { sNode = uncompress_string(strNode, size, &uncompressed_len); Assert(sNode != NULL); node = readNodeFromBinaryString(sNode, uncompressed_len); pfree(sNode); } END_MEMORY_ACCOUNT(); return node; }
/* * InitMemoryAccounting * Internal method that should only be used to initialize a memory accounting * subsystem. Creates basic data structure such as the MemoryAccountMemoryContext, * TopMemoryAccount, MemoryAccountMemoryAccount */ static void InitMemoryAccounting() { Assert(TopMemoryAccount == NULL); Assert(AlienExecutorMemoryAccount == NULL); /* * Order of creation: * * 1. Root * 2. SharedChunkHeadersMemoryAccount * 3. RolloverMemoryAccount * 4. MemoryAccountMemoryAccount * 5. MemoryAccountMemoryContext * * The above 5 survive reset (MemoryAccountMemoryContext * gets reset, but not deleted). * * Next set ActiveMemoryAccount = MemoryAccountMemoryAccount * * Note, don't set MemoryAccountMemoryAccount before creating * MemoryAccuontMemoryContext, as that will put the balance * of MemoryAccountMemoryContext in the MemoryAccountMemoryAccount, * preventing it from going to 0, upon reset of MemoryAccountMemoryContext. * MemoryAccountMemoryAccount should only contain balance from actual * account creation, not the overhead of the context. * * Once the long living accounts are done and MemoryAccountMemoryAccount * is the ActiveMemoryAccount, we proceed to create TopMemoryAccount * * This ensures the following: * * 1. Accounting is triggered only if the ActiveMemoryAccount is non-null * * 2. If the ActiveMemoryAccount is non-null, we are guaranteed to have * SharedChunkHeadersMemoryAccount, so that we can account shared chunk headers * * 3. All short-living accounts (including Top) are accounted in MemoryAccountMemoryAccount * * 4. All short-living accounts are allocated in MemoryAccountMemoryContext * * 5. Number of allocations in the aset.c with nullAccountHeader is very small * as we turn on ActiveMemoryAccount immediately after we allocate long-living * accounts (only the memory to host long-living accounts are unaccounted, which * are very few and their overhead is already known). */ if (MemoryAccountTreeLogicalRoot == NULL) { /* * All the long living accounts are created together, so if logical root * is null, then other long-living accounts should be the null too */ Assert(SharedChunkHeadersMemoryAccount == NULL && RolloverMemoryAccount == NULL); Assert(TopMemoryAccount == NULL && MemoryAccountMemoryAccount == NULL && MemoryAccountMemoryContext == NULL); MemoryAccountTreeLogicalRoot = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_LogicalRoot); SharedChunkHeadersMemoryAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_SharedChunkHeader); RolloverMemoryAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Rollover); MemoryAccountMemoryAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_MemAccount); /* Now initiate the memory accounting system. */ MemoryAccountMemoryContext = AllocSetContextCreate(TopMemoryContext, "MemoryAccountMemoryContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * Temporarily active MemoryAccountMemoryAccount. Once * all the setup is done, TopMemoryAccount will become * the ActiveMemoryAccount */ ActiveMemoryAccount = MemoryAccountMemoryAccount; } else { /* Long-living setup is already done, so re-initialize those */ /* If "logical root" is pre-existing, "rollover" should also be pre-existing */ Assert(MemoryAccountTreeLogicalRoot->nextSibling == NULL && RolloverMemoryAccount != NULL && SharedChunkHeadersMemoryAccount != NULL && MemoryAccountMemoryAccount != NULL); /* Ensure tree integrity */ Assert(MemoryAccountMemoryAccount->firstChild == NULL && SharedChunkHeadersMemoryAccount->firstChild == NULL && RolloverMemoryAccount->firstChild == NULL); /* * First child of MemoryAccountTreeLogicalRoot should be Top, which * had been obliterated during MemoryAccountMemoryContextReset. So, * we don't attempt to verify the first child of MeomryAccountTreeLogicalRoot */ Assert(MemoryAccountMemoryAccount->nextSibling == RolloverMemoryAccount && RolloverMemoryAccount->nextSibling == SharedChunkHeadersMemoryAccount && SharedChunkHeadersMemoryAccount->nextSibling == NULL); /* * We will loose previous TopMemoryAccount during InitMemoryAccounting. So, * we need to readjust the "logical root" children pointers. */ MemoryAccountTreeLogicalRoot->firstChild = MemoryAccountMemoryAccount; MemoryAccountMemoryAccount->nextSibling = RolloverMemoryAccount; RolloverMemoryAccount->nextSibling = SharedChunkHeadersMemoryAccount; SharedChunkHeadersMemoryAccount->nextSibling = NULL; RolloverMemoryAccount->firstChild = NULL; SharedChunkHeadersMemoryAccount->firstChild = NULL; } TopMemoryAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Top); /* For AlienExecutorMemoryAccount we need TopMemoryAccount as parent */ ActiveMemoryAccount = TopMemoryAccount; AlienExecutorMemoryAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_AlienShared); }
/* * Tests whether we correctly allocate a new shared header and insert it * into the sharedHeaderList */ void test__AllocAllocInfo__InsertsIntoSharedHeaderList(void **state) { AllocSet set = (AllocSet)CurrentMemoryContext; /* * This should create or reuse one of the sharedHeader * in the current context's sharedHeaderList */ void *testAlloc1 = palloc(NEW_ALLOC_SIZE); MemoryAccount *newActiveAccount1 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account to force a new shared header allocation */ MemoryAccounting_SwitchAccount(newActiveAccount1); /* This will trigger a new sharedHeader creation because of the new ActiveMemoryAccount */ void *testAlloc2 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header1 = (StandardChunkHeader *) ((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE); StandardChunkHeader *header2 = (StandardChunkHeader *) ((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE); /* Now we are triggering a third sharedHeader creation */ MemoryAccount *newActiveAccount2 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account to force a new shared header allocation */ MemoryAccounting_SwitchAccount(newActiveAccount2); void *testAlloc3 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header3 = (StandardChunkHeader *) ((char *) testAlloc3 - STANDARDCHUNKHEADERSIZE); /* Verify that the sharedHeaderList linked list looks good */ assert_true(set->sharedHeaderList == header3->sharedHeader && set->sharedHeaderList->next == header2->sharedHeader && set->sharedHeaderList->next->next == header1->sharedHeader && set->sharedHeaderList->next->next->next == NULL); /* * Create one more account to force creation of another sharedHeader, which * should replace the earliest sharedHeader in the sharedHeaderList. We only * look ahead up to depth 3, but we maintain all the sharedHeaders in the * sharedHeaderList */ MemoryAccount *newActiveAccount3 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); MemoryAccounting_SwitchAccount(newActiveAccount3); void *testAlloc4 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header4 = (StandardChunkHeader *) ((char *) testAlloc4 - STANDARDCHUNKHEADERSIZE); /* * Verify that the sharedHeaderList linked list can go beyond * 3 levels of lookahead by testing linked list up to depth 4 */ assert_true(set->sharedHeaderList == header4->sharedHeader && set->sharedHeaderList->next == header3->sharedHeader && set->sharedHeaderList->next->next == header2->sharedHeader && set->sharedHeaderList->next->next->next == header1->sharedHeader && set->sharedHeaderList->next->next->next->next == NULL); pfree(testAlloc1); pfree(testAlloc2); pfree(testAlloc3); pfree(testAlloc4); }
/* * Tests whether header sharing works even if the desired header * is not at the head of sharedHeaderList */ void test__AllocAllocInfo__LooksAheadInSharedHeaderList(void **state) { AllocSet set = (AllocSet)CurrentMemoryContext; /* * This should create or reuse one of the sharedHeader * in the current context's sharedHeaderList */ void *testAlloc1 = palloc(NEW_ALLOC_SIZE); MemoryAccount *newActiveAccount1 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account to force a new shared header allocation */ MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount1); /* This will trigger a new sharedHeader creation because of the new ActiveMemoryAccount */ void *testAlloc2 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header1 = (StandardChunkHeader *) ((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE); StandardChunkHeader *header2 = (StandardChunkHeader *) ((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE); /* The old shared header should not be at the head of sharedHeaderList */ assert_true(set->sharedHeaderList != header1->sharedHeader); assert_true(header1->sharedHeader != header2->sharedHeader); /* * This should restore old active account, so any further allocation * should reuse header1, which is now *not* at the head of sharedHeaderList */ MemoryAccounting_SwitchAccount(oldActiveAccount); void *testAlloc3 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header3 = (StandardChunkHeader *) ((char *) testAlloc3 - STANDARDCHUNKHEADERSIZE); /* As we switched to previous account, we should be able to share the header */ assert_true(header3->sharedHeader == header1->sharedHeader); /* Now we are triggering a third sharedHeader creation */ MemoryAccount *newActiveAccount2 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash); /* Make sure we have a new active account to force a new shared header allocation */ oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount2); void *testAlloc4 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header4 = (StandardChunkHeader *) ((char *) testAlloc4 - STANDARDCHUNKHEADERSIZE); /* Make sure header4 got a new sharedHeader as we switched to a *new* memory account */ assert_true(header4->sharedHeader != header1->sharedHeader && header4->sharedHeader != header2->sharedHeader && header4->sharedHeader != header1->sharedHeader); /* And the latest sharedHeader should be at the head of sharedHeaderList */ assert_true(header4->sharedHeader == set->sharedHeaderList); /* * Now restore the original account, making the very original * sharedHeader eligible to be reused (a 3 level reuse lookahead) */ MemoryAccounting_SwitchAccount(oldActiveAccount); void *testAlloc5 = palloc(NEW_ALLOC_SIZE); StandardChunkHeader *header5 = (StandardChunkHeader *) ((char *) testAlloc5 - STANDARDCHUNKHEADERSIZE); /* * Make sure we were able to dig up the original sharedHeader * by digging through the sharedHeaderList */ assert_true(header5->sharedHeader == header1->sharedHeader); pfree(testAlloc1); pfree(testAlloc2); pfree(testAlloc3); pfree(testAlloc4); pfree(testAlloc5); }