mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-27 06:43:04 +01:00
1267 lines
34 KiB
C++
1267 lines
34 KiB
C++
/*
|
|
* The contents of this file are subject to the Initial
|
|
* Developer's Public License Version 1.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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
|
*
|
|
* Software distributed under the License is distributed AS IS,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing rights
|
|
* and limitations under the License.
|
|
*
|
|
* The Original Code was created by Dmitry Yemanov
|
|
* for the Firebird Open Source RDBMS project.
|
|
*
|
|
* Copyright (c) 2006 Dmitry Yemanov <dimitr@users.sf.net>
|
|
* and all contributors signed below.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "ids.h"
|
|
|
|
#include "../common/classes/auto.h"
|
|
#include "../common/classes/locks.h"
|
|
|
|
#include "../jrd/gdsassert.h"
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/cch.h"
|
|
#include "../jrd/ini.h"
|
|
#include "../jrd/nbak.h"
|
|
#include "../jrd/os/guid.h"
|
|
#include "../jrd/os/pio.h"
|
|
#include "../jrd/req.h"
|
|
#include "../jrd/tra.h"
|
|
#include "../jrd/blb_proto.h"
|
|
#include "../jrd/isc_proto.h"
|
|
#include "../jrd/isc_s_proto.h"
|
|
#include "../jrd/lck_proto.h"
|
|
#include "../jrd/met_proto.h"
|
|
#include "../jrd/os/pio_proto.h"
|
|
#include "../jrd/pag_proto.h"
|
|
#include "../jrd/thread_proto.h"
|
|
|
|
#include "../jrd/Relation.h"
|
|
#include "../jrd/RecordBuffer.h"
|
|
#include "../jrd/DatabaseSnapshot.h"
|
|
|
|
#include "../common/utils_proto.h"
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef UNIX
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
#include <process.h>
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
|
|
using namespace Firebird;
|
|
using namespace Jrd;
|
|
|
|
const UCHAR TAG_RECORD = MAX_UCHAR;
|
|
|
|
|
|
// SharedMemory class
|
|
|
|
const ULONG DatabaseSnapshot::SharedMemory::VERSION = 2;
|
|
const ULONG DatabaseSnapshot::SharedMemory::DEFAULT_SIZE = 1048576;
|
|
|
|
|
|
DatabaseSnapshot::SharedMemory::SharedMemory()
|
|
{
|
|
TEXT filename[MAXPATHLEN];
|
|
gds__prefix_lock(filename, MONITOR_FILE);
|
|
|
|
ISC_STATUS_ARRAY statusVector;
|
|
base = (Header*) ISC_map_file(statusVector, filename, init, this, DEFAULT_SIZE, &handle);
|
|
if (!base)
|
|
{
|
|
iscLogStatus("Cannot initialize the shared memory region", statusVector);
|
|
status_exception::raise(statusVector);
|
|
}
|
|
|
|
fb_assert(base->version == VERSION);
|
|
}
|
|
|
|
|
|
DatabaseSnapshot::SharedMemory::~SharedMemory()
|
|
{
|
|
ISC_STATUS_ARRAY statusVector;
|
|
ISC_unmap_file(statusVector, &handle);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::acquire()
|
|
{
|
|
#ifdef WIN_NT
|
|
checkMutex("lock", ISC_mutex_lock(&mutex));
|
|
#else
|
|
checkMutex("lock", ISC_mutex_lock(&base->mutex));
|
|
#endif
|
|
if (base->allocated > handle.sh_mem_length_mapped)
|
|
{
|
|
#if (defined HAVE_MMAP || defined WIN_NT)
|
|
ISC_STATUS_ARRAY statusVector;
|
|
base = (Header*) ISC_remap_file(statusVector, &handle, base->allocated, false);
|
|
if (!base)
|
|
{
|
|
status_exception::raise(statusVector);
|
|
}
|
|
#else
|
|
status_exception::raise(Arg::Gds(isc_montabexh));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::release()
|
|
{
|
|
#ifdef WIN_NT
|
|
checkMutex("unlock", ISC_mutex_unlock(&mutex));
|
|
#else
|
|
checkMutex("unlock", ISC_mutex_unlock(&base->mutex));
|
|
#endif
|
|
}
|
|
|
|
|
|
UCHAR* DatabaseSnapshot::SharedMemory::readData(thread_db* tdbb, MemoryPool& pool, ULONG& resultSize)
|
|
{
|
|
fb_assert(tdbb);
|
|
|
|
const Database* const dbb = tdbb->getDatabase();
|
|
fb_assert(dbb);
|
|
|
|
DumpGuard guard(this);
|
|
|
|
ULONG self_dbb_offset = 0;
|
|
|
|
// Garbage collect elements belonging to dead processes.
|
|
// This is done in two passes. First, we compact the data
|
|
// and calculate the total size of the resulting data.
|
|
// Second, we create a resulting buffer of the necessary size
|
|
// and copy the data there, starting with our own dbb.
|
|
|
|
// First pass
|
|
for (ULONG offset = sizeof(Header); offset < base->used;)
|
|
{
|
|
UCHAR* const ptr = (UCHAR*) base + offset;
|
|
const Element* const element = (Element*) ptr;
|
|
const ULONG length = sizeof(Element) + element->length;
|
|
|
|
if (element->processId == getpid() && element->localId == dbb->dbb_monitoring_id)
|
|
{
|
|
self_dbb_offset = offset;
|
|
}
|
|
|
|
if (ISC_check_process_existence(element->processId))
|
|
{
|
|
resultSize += element->length;
|
|
offset += length;
|
|
}
|
|
else
|
|
{
|
|
fb_assert(base->used >= offset + length);
|
|
memmove(ptr, ptr + length, base->used - offset - length);
|
|
base->used -= length;
|
|
}
|
|
}
|
|
|
|
// Second pass
|
|
UCHAR* const buffer = FB_NEW(pool) UCHAR[resultSize];
|
|
UCHAR* bufferPtr(buffer);
|
|
|
|
fb_assert(self_dbb_offset);
|
|
|
|
UCHAR* const ptr = (UCHAR*) base + self_dbb_offset;
|
|
const Element* const element = (Element*) ptr;
|
|
memcpy(bufferPtr, ptr + sizeof(Element), element->length);
|
|
bufferPtr += element->length;
|
|
|
|
for (ULONG offset = sizeof(Header); offset < base->used;)
|
|
{
|
|
UCHAR* const ptr = (UCHAR*) base + offset;
|
|
const Element* const element = (Element*) ptr;
|
|
const ULONG length = sizeof(Element) + element->length;
|
|
|
|
if (offset != self_dbb_offset)
|
|
{
|
|
memcpy(bufferPtr, ptr + sizeof(Element), element->length);
|
|
bufferPtr += element->length;
|
|
}
|
|
|
|
offset += length;
|
|
}
|
|
|
|
fb_assert(buffer + resultSize == bufferPtr);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::writeData(thread_db* tdbb, ULONG length, const UCHAR* buffer)
|
|
{
|
|
fb_assert(tdbb);
|
|
|
|
const Database* const dbb = tdbb->getDatabase();
|
|
fb_assert(dbb);
|
|
|
|
DumpGuard guard(this);
|
|
|
|
// Remove old copies of our element, if any
|
|
doCleanup(dbb);
|
|
|
|
// Do we need to extend the allocated memory?
|
|
while (base->used + sizeof(Element) + length > base->allocated)
|
|
{
|
|
extend();
|
|
}
|
|
|
|
// Put an up-to-date element at the tail
|
|
UCHAR* const ptr = (UCHAR*) base + base->used;
|
|
Element* const element = (Element*) ptr;
|
|
element->processId = getpid();
|
|
element->localId = dbb->dbb_monitoring_id;
|
|
element->length = length;
|
|
memcpy(ptr + sizeof(Element), buffer, length);
|
|
base->used += sizeof(Element) + length;
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::cleanup(thread_db* tdbb)
|
|
{
|
|
fb_assert(tdbb);
|
|
|
|
const Database* const dbb = tdbb->getDatabase();
|
|
fb_assert(dbb);
|
|
|
|
DumpGuard guard(this);
|
|
|
|
// Remove information about our dbb
|
|
doCleanup(dbb);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::doCleanup(const Database* const dbb)
|
|
{
|
|
for (ULONG offset = sizeof(Header); offset < base->used;)
|
|
{
|
|
UCHAR* const ptr = (UCHAR*) base + offset;
|
|
const Element* const element = (Element*) ptr;
|
|
const ULONG length = sizeof(Element) + element->length;
|
|
|
|
if (element->processId == getpid() && element->localId == dbb->dbb_monitoring_id)
|
|
{
|
|
fb_assert(base->used >= offset + length);
|
|
memmove(ptr, ptr + length, base->used - offset - length);
|
|
base->used -= length;
|
|
}
|
|
else
|
|
{
|
|
offset += length;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::extend()
|
|
{
|
|
const ULONG newSize = handle.sh_mem_length_mapped + DEFAULT_SIZE;
|
|
|
|
#if (defined HAVE_MMAP || defined WIN_NT)
|
|
ISC_STATUS_ARRAY statusVector;
|
|
base = (Header*) ISC_remap_file(statusVector, &handle, newSize, true);
|
|
if (!base)
|
|
{
|
|
status_exception::raise(statusVector);
|
|
}
|
|
base->allocated = handle.sh_mem_length_mapped;
|
|
#else
|
|
status_exception::raise(Arg::Gds(isc_montabexh));
|
|
#endif
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::checkMutex(const TEXT* string, int state)
|
|
{
|
|
if (state)
|
|
{
|
|
TEXT msg[BUFFER_TINY];
|
|
|
|
sprintf(msg, "MONITOR: mutex %s error, status = %d", string, state);
|
|
gds__log(msg);
|
|
|
|
fprintf(stderr, "%s\n", msg);
|
|
exit(FINI_ERROR);
|
|
}
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::SharedMemory::init(void* arg, SH_MEM_T* shmemData, bool initialize)
|
|
{
|
|
SharedMemory* const shmem = (SharedMemory*) arg;
|
|
fb_assert(shmem);
|
|
|
|
#ifdef WIN_NT
|
|
char buffer[MAXPATHLEN];
|
|
gds__prefix_lock(buffer, MONITOR_FILE);
|
|
checkMutex("init", ISC_mutex_init(&shmem->mutex, buffer));
|
|
#endif
|
|
|
|
if (!initialize)
|
|
return;
|
|
|
|
// Initialize the shared data header
|
|
Header* const header = (Header*) shmemData->sh_mem_address;
|
|
header->version = VERSION;
|
|
header->used = sizeof(Header);
|
|
header->allocated = shmemData->sh_mem_length_mapped;
|
|
|
|
#ifndef WIN_NT
|
|
checkMutex("init", ISC_mutex_init(&header->mutex));
|
|
#endif
|
|
}
|
|
|
|
|
|
// DatabaseSnapshot class
|
|
|
|
DatabaseSnapshot::SharedMemory* DatabaseSnapshot::dump = NULL;
|
|
InitMutex<DatabaseSnapshot> DatabaseSnapshot::startup;
|
|
|
|
|
|
DatabaseSnapshot* DatabaseSnapshot::create(thread_db* tdbb)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
jrd_tra* transaction = tdbb->getTransaction();
|
|
fb_assert(transaction);
|
|
|
|
if (!transaction->tra_db_snapshot)
|
|
{
|
|
// Create a database snapshot and store it
|
|
// in the transaction block
|
|
MemoryPool& pool = *transaction->tra_pool;
|
|
transaction->tra_db_snapshot = FB_NEW(pool) DatabaseSnapshot(tdbb, pool);
|
|
}
|
|
|
|
return transaction->tra_db_snapshot;
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::cleanup(thread_db* tdbb)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
if (dump)
|
|
{
|
|
dump->cleanup(tdbb);
|
|
}
|
|
}
|
|
|
|
|
|
int DatabaseSnapshot::blockingAst(void* ast_object)
|
|
{
|
|
Database* dbb = static_cast<Database*>(ast_object);
|
|
|
|
try
|
|
{
|
|
Lock* const lock = dbb->dbb_monitor_lock;
|
|
|
|
Database::SyncGuard dsGuard(dbb, true);
|
|
|
|
ThreadContextHolder tdbb;
|
|
tdbb->setDatabase(lock->lck_dbb);
|
|
tdbb->setAttachment(lock->lck_attachment);
|
|
|
|
ContextPoolHolder context(tdbb, dbb->dbb_permanent);
|
|
|
|
if (!(dbb->dbb_ast_flags & DBB_monitor_off))
|
|
{
|
|
try {
|
|
// Write the data to the shared memory
|
|
dumpData(tdbb);
|
|
|
|
// Release the lock and mark dbb as requesting a new one
|
|
LCK_release(tdbb, lock);
|
|
dbb->dbb_ast_flags |= DBB_monitor_off;
|
|
}
|
|
catch (const Exception&) {
|
|
gds__log("Unexpected exception at the AST level");
|
|
}
|
|
}
|
|
}
|
|
catch (const Exception&)
|
|
{} // no-op
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
DatabaseSnapshot::DatabaseSnapshot(thread_db* tdbb, MemoryPool& pool)
|
|
: snapshot(pool), idMap(pool), idCounter(0)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
PAG_header(tdbb, true);
|
|
|
|
Database* const dbb = tdbb->getDatabase();
|
|
fb_assert(dbb);
|
|
|
|
const USHORT ods_version = ENCODE_ODS(dbb->dbb_ods_version, dbb->dbb_minor_original);
|
|
|
|
// Initialize record buffers
|
|
RecordBuffer* const dbb_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_database) : NULL;
|
|
RecordBuffer* const att_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_attachments) : NULL;
|
|
RecordBuffer* const tra_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_transactions) : NULL;
|
|
RecordBuffer* const stmt_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_statements) : NULL;
|
|
RecordBuffer* const call_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_calls) : NULL;
|
|
RecordBuffer* const io_stat_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_io_stats) : NULL;
|
|
RecordBuffer* const rec_stat_buffer =
|
|
ods_version >= ODS_11_1 ? allocBuffer(tdbb, pool, rel_mon_rec_stats) : NULL;
|
|
RecordBuffer* const ctx_var_buffer =
|
|
ods_version >= ODS_11_2 ? allocBuffer(tdbb, pool, rel_mon_ctx_vars) : NULL;
|
|
RecordBuffer* const mem_usage_buffer =
|
|
ods_version >= ODS_11_2 ? allocBuffer(tdbb, pool, rel_mon_mem_usage) : NULL;
|
|
|
|
// Release our own lock
|
|
LCK_release(tdbb, dbb->dbb_monitor_lock);
|
|
|
|
// Dump our own data
|
|
dumpData(tdbb);
|
|
|
|
// Signal other processes to dump their data
|
|
Lock temp_lock, *lock = &temp_lock;
|
|
lock->lck_dbb = dbb;
|
|
lock->lck_length = sizeof(SLONG);
|
|
lock->lck_key.lck_long = 0;
|
|
lock->lck_type = LCK_monitor;
|
|
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
|
|
lock->lck_parent = dbb->dbb_lock;
|
|
|
|
if (LCK_lock(tdbb, lock, LCK_EX, LCK_WAIT))
|
|
LCK_release(tdbb, lock);
|
|
|
|
// Mark dbb as requesting a new lock
|
|
dbb->dbb_ast_flags |= DBB_monitor_off;
|
|
|
|
// Read the shared memory
|
|
fb_assert(dump);
|
|
ULONG dataSize = 0;
|
|
AutoPtr<UCHAR, ArrayDelete<UCHAR> > data(dump->readData(tdbb, pool, dataSize));
|
|
fb_assert(dataSize);
|
|
|
|
ClumpletReader reader(ClumpletReader::WideUnTagged, data, dataSize);
|
|
|
|
const Attachment* const attachment = tdbb->getAttachment();
|
|
fb_assert(attachment);
|
|
const PathName& databaseName = dbb->dbb_database_name;
|
|
const string& userName = attachment->att_user->usr_user_name;
|
|
const bool locksmith = attachment->locksmith();
|
|
|
|
// Parse the dump
|
|
RecordBuffer* buffer = NULL;
|
|
Record* record = NULL;
|
|
|
|
int rid = 0;
|
|
bool dbb_processed = false, fields_processed = false;
|
|
bool dbb_allowed = false, att_allowed = false;
|
|
|
|
for (reader.rewind(); !reader.isEof(); reader.moveNext())
|
|
{
|
|
if (reader.getClumpTag() == TAG_RECORD)
|
|
{
|
|
rid = reader.getInt();
|
|
|
|
if (fields_processed)
|
|
{
|
|
buffer->store(record);
|
|
fields_processed = false;
|
|
}
|
|
|
|
switch (rid)
|
|
{
|
|
case rel_mon_database:
|
|
buffer = dbb_buffer;
|
|
break;
|
|
case rel_mon_attachments:
|
|
buffer = att_buffer;
|
|
break;
|
|
case rel_mon_transactions:
|
|
buffer = tra_buffer;
|
|
break;
|
|
case rel_mon_statements:
|
|
buffer = stmt_buffer;
|
|
break;
|
|
case rel_mon_calls:
|
|
buffer = call_buffer;
|
|
break;
|
|
case rel_mon_io_stats:
|
|
buffer = io_stat_buffer;
|
|
break;
|
|
case rel_mon_rec_stats:
|
|
buffer = rec_stat_buffer;
|
|
break;
|
|
case rel_mon_ctx_vars:
|
|
buffer = ctx_var_buffer;
|
|
break;
|
|
case rel_mon_mem_usage:
|
|
buffer = mem_usage_buffer;
|
|
break;
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
if (buffer)
|
|
{
|
|
record = buffer->getTempRecord();
|
|
clearRecord(record);
|
|
}
|
|
else
|
|
{
|
|
record = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int fid = reader.getClumpTag();
|
|
const size_t length = reader.getClumpLength();
|
|
|
|
const char* source = checkNull(rid, fid, (char*) reader.getBytes(), length);
|
|
|
|
// special case for MON$DATABASE
|
|
if (rid == rel_mon_database)
|
|
{
|
|
if (fid == f_mon_db_name)
|
|
{
|
|
dbb_allowed = !databaseName.compare(source, length);
|
|
}
|
|
|
|
if (record && dbb_allowed && !dbb_processed)
|
|
{
|
|
putField(record, fid, reader, source == NULL);
|
|
fields_processed = true;
|
|
}
|
|
|
|
att_allowed = (dbb_allowed && !dbb_processed);
|
|
}
|
|
// special case for MON$ATTACHMENTS
|
|
else if (rid == rel_mon_attachments)
|
|
{
|
|
if (fid == f_mon_att_user)
|
|
{
|
|
att_allowed = locksmith || !userName.compare(source, length);
|
|
}
|
|
|
|
if (record && dbb_allowed && att_allowed)
|
|
{
|
|
putField(record, fid, reader, source == NULL);
|
|
fields_processed = true;
|
|
dbb_processed = true;
|
|
}
|
|
}
|
|
// generic logic that covers all other relations
|
|
else if (record && dbb_allowed && att_allowed)
|
|
{
|
|
putField(record, fid, reader, source == NULL);
|
|
fields_processed = true;
|
|
dbb_processed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fields_processed)
|
|
{
|
|
buffer->store(record);
|
|
}
|
|
}
|
|
|
|
|
|
DatabaseSnapshot::~DatabaseSnapshot()
|
|
{
|
|
for (size_t i = 0; i < snapshot.getCount(); i++)
|
|
{
|
|
delete snapshot[i].data;
|
|
}
|
|
}
|
|
|
|
|
|
RecordBuffer* DatabaseSnapshot::getData(const jrd_rel* relation) const
|
|
{
|
|
fb_assert(relation);
|
|
|
|
for (size_t i = 0; i < snapshot.getCount(); i++)
|
|
{
|
|
if (snapshot[i].rel_id == relation->rel_id)
|
|
return snapshot[i].data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
RecordBuffer* DatabaseSnapshot::allocBuffer(thread_db* tdbb, MemoryPool& pool, int rel_id)
|
|
{
|
|
jrd_rel* relation = MET_lookup_relation_id(tdbb, rel_id, false);
|
|
fb_assert(relation);
|
|
MET_scan_relation(tdbb, relation);
|
|
fb_assert(relation->isVirtual());
|
|
Format* format = MET_current(tdbb, relation);
|
|
fb_assert(format);
|
|
|
|
RecordBuffer* buffer = FB_NEW(pool) RecordBuffer(pool, format);
|
|
RelationData data = {relation->rel_id, buffer};
|
|
snapshot.add(data);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::clearRecord(Record* record)
|
|
{
|
|
fb_assert(record);
|
|
|
|
// Initialize all fields to NULLs
|
|
memset(record->rec_data, 0, record->rec_length);
|
|
const size_t null_bytes = (record->rec_format->fmt_count + 7) >> 3;
|
|
memset(record->rec_data, 0xFF, null_bytes);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::putField(Record* record, int id, const ClumpletReader& reader, bool makeNull)
|
|
{
|
|
fb_assert(record);
|
|
|
|
const Format* const format = record->rec_format;
|
|
fb_assert(format && id < format->fmt_count);
|
|
|
|
if (makeNull)
|
|
{
|
|
SET_NULL(record, id);
|
|
return;
|
|
}
|
|
|
|
const dsc desc = format->fmt_desc[id];
|
|
UCHAR* const address = record->rec_data + (IPTR) desc.dsc_address;
|
|
|
|
if (reader.getClumpLength() == sizeof(SINT64) && desc.dsc_dtype == dtype_long)
|
|
{
|
|
// special case: translate 64-bit global ID into 32-bit local ID
|
|
const SINT64 global_id = reader.getBigInt();
|
|
SLONG local_id = 0;
|
|
if (!idMap.get(global_id, local_id))
|
|
{
|
|
local_id = ++idCounter;
|
|
idMap.put(global_id, local_id);
|
|
}
|
|
*(SLONG*) address = local_id;
|
|
CLEAR_NULL(record, id);
|
|
return;
|
|
}
|
|
|
|
switch (desc.dsc_dtype)
|
|
{
|
|
case dtype_text:
|
|
{
|
|
const char* const string = (char*) reader.getBytes();
|
|
const size_t max_length = desc.dsc_length;
|
|
const size_t length = MIN(reader.getClumpLength(), max_length);
|
|
memcpy(address, string, length);
|
|
memset(address + length, ' ', max_length - length);
|
|
}
|
|
break;
|
|
case dtype_varying:
|
|
{
|
|
const char* const string = (char*) reader.getBytes();
|
|
const size_t max_length = desc.dsc_length - sizeof(USHORT);
|
|
const size_t length = MIN(reader.getClumpLength(), max_length);
|
|
vary* varying = (vary*) address;
|
|
varying->vary_length = length;
|
|
memcpy(varying->vary_string, string, length);
|
|
}
|
|
break;
|
|
|
|
case dtype_short:
|
|
*(SSHORT*) address = reader.getBigInt();
|
|
break;
|
|
case dtype_long:
|
|
*(SLONG*) address = reader.getBigInt();
|
|
break;
|
|
case dtype_int64:
|
|
*(SINT64*) address = reader.getBigInt();
|
|
break;
|
|
|
|
case dtype_real:
|
|
*(float*) address = reader.getDouble();
|
|
break;
|
|
case dtype_double:
|
|
*(double*) address = reader.getDouble();
|
|
break;
|
|
|
|
case dtype_sql_date:
|
|
*(ISC_DATE*) address = reader.getDate();
|
|
break;
|
|
case dtype_sql_time:
|
|
*(ISC_TIME*) address = reader.getTime();
|
|
break;
|
|
case dtype_timestamp:
|
|
*(ISC_TIMESTAMP*) address = reader.getTimeStamp();
|
|
break;
|
|
|
|
case dtype_blob:
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
UCharBuffer bpb;
|
|
bpb.resize(15);
|
|
|
|
UCHAR* p = bpb.begin();
|
|
*p++ = isc_bpb_version1;
|
|
|
|
*p++ = isc_bpb_source_type;
|
|
*p++ = 2;
|
|
put_vax_short(p, isc_blob_text);
|
|
p += 2;
|
|
*p++ = isc_bpb_source_interp;
|
|
*p++ = 1;
|
|
*p++ = tdbb->getAttachment()->att_charset;
|
|
|
|
*p++ = isc_bpb_target_type;
|
|
*p++ = 2;
|
|
put_vax_short(p, isc_blob_text);
|
|
p += 2;
|
|
*p++ = isc_bpb_target_interp;
|
|
*p++ = 1;
|
|
*p++ = CS_METADATA;
|
|
|
|
bpb.shrink(p - bpb.begin());
|
|
|
|
bid blob_id;
|
|
blb* blob = BLB_create2(tdbb, tdbb->getTransaction(), &blob_id,
|
|
bpb.getCount(), bpb.begin());
|
|
|
|
const size_t length = MIN(reader.getClumpLength(), MAX_USHORT);
|
|
|
|
BLB_put_segment(tdbb, blob, reader.getBytes(), length);
|
|
BLB_close(tdbb, blob);
|
|
|
|
*(bid*) address = blob_id;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
CLEAR_NULL(record, id);
|
|
}
|
|
|
|
|
|
const char* DatabaseSnapshot::checkNull(int rid, int fid, const char* source, size_t length)
|
|
{
|
|
// The only goal of this function is to substitute some numeric zeroes
|
|
// and empty strings with NULLs
|
|
|
|
switch (rid)
|
|
{
|
|
case rel_mon_attachments:
|
|
switch (fid)
|
|
{
|
|
case f_mon_att_remote_proto:
|
|
case f_mon_att_remote_addr:
|
|
case f_mon_att_remote_process:
|
|
return length ? source : NULL;
|
|
case f_mon_att_remote_pid:
|
|
return (*(SLONG*) source) ? source : NULL;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case rel_mon_statements:
|
|
switch (fid)
|
|
{
|
|
case f_mon_stmt_att_id:
|
|
case f_mon_stmt_tra_id:
|
|
return (*(SLONG*) source) ? source : NULL;
|
|
case f_mon_stmt_timestamp:
|
|
return (*(SINT64*) source) ? source : NULL;
|
|
case f_mon_stmt_sql_text:
|
|
return length ? source : NULL;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case rel_mon_calls:
|
|
switch (fid)
|
|
{
|
|
case f_mon_call_caller_id:
|
|
return (*(SINT64*) source) ? source : NULL;
|
|
case f_mon_call_type:
|
|
case f_mon_call_src_line:
|
|
case f_mon_call_src_column:
|
|
return (*(SLONG*) source) ? source : NULL;
|
|
case f_mon_call_name:
|
|
return length ? source : NULL;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case rel_mon_mem_usage:
|
|
switch (fid)
|
|
{
|
|
case f_mon_mem_cur_alloc:
|
|
case f_mon_mem_max_alloc:
|
|
return (*(SLONG*) source) ? source : NULL;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return source;
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::dumpData(thread_db* tdbb)
|
|
{
|
|
fb_assert(tdbb);
|
|
|
|
Database* const dbb = tdbb->getDatabase();
|
|
fb_assert(dbb);
|
|
|
|
ClumpletWriter writer(ClumpletReader::WideUnTagged, MAX_ULONG);
|
|
|
|
// Database information
|
|
|
|
putDatabase(dbb, writer, fb_utils::genUniqueId());
|
|
|
|
// Attachment information
|
|
|
|
for (Attachment* attachment = dbb->dbb_attachments; attachment; attachment = attachment->att_next)
|
|
{
|
|
putAttachment(attachment, writer, fb_utils::genUniqueId());
|
|
putContextVars(attachment->att_context_vars, writer, attachment->att_attachment_id, true);
|
|
|
|
jrd_tra* transaction = NULL;
|
|
jrd_req* request = NULL;
|
|
|
|
// Transaction information
|
|
|
|
for (transaction = attachment->att_transactions;
|
|
transaction; transaction = transaction->tra_next)
|
|
{
|
|
putTransaction(transaction, writer, fb_utils::genUniqueId());
|
|
putContextVars(transaction->tra_context_vars, writer, transaction->tra_number, false);
|
|
}
|
|
|
|
// Call stack information
|
|
|
|
for (transaction = attachment->att_transactions;
|
|
transaction; transaction = transaction->tra_next)
|
|
{
|
|
for (request = transaction->tra_requests; request; request = request->req_caller)
|
|
{
|
|
request->adjustCallerStats();
|
|
|
|
if (!(request->req_flags & (req_internal | req_sys_trigger)) && request->req_caller)
|
|
{
|
|
putCall(request, writer, fb_utils::genUniqueId());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request information
|
|
|
|
for (request = attachment->att_requests; request; request = request->req_request)
|
|
{
|
|
if (!(request->req_flags & (req_internal | req_sys_trigger)))
|
|
{
|
|
putRequest(request, writer, fb_utils::genUniqueId());
|
|
}
|
|
}
|
|
}
|
|
|
|
startup.init();
|
|
|
|
fb_assert(dump);
|
|
dump->writeData(tdbb, writer.getBufferLength(), writer.getBuffer());
|
|
}
|
|
|
|
|
|
SINT64 DatabaseSnapshot::getGlobalId(int value)
|
|
{
|
|
return ((SINT64) getpid() << BITS_PER_LONG) + value;
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::putDatabase(const Database* database, ClumpletWriter& writer, int stat_id)
|
|
{
|
|
fb_assert(database);
|
|
|
|
writer.insertByte(TAG_RECORD, rel_mon_database);
|
|
|
|
// Reload header
|
|
// CVC: I don't see the need for this call. If it produces a side effect that's necessary here,
|
|
// then calling the function without assigning its result value may be clearer.
|
|
//const PageSpace* const pageSpace = database->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
|
|
// database name or alias (MUST BE ALWAYS THE FIRST ITEM PASSED!)
|
|
writer.insertPath(f_mon_db_name, database->dbb_database_name);
|
|
// page size
|
|
writer.insertInt(f_mon_db_page_size, database->dbb_page_size);
|
|
// major ODS version
|
|
writer.insertInt(f_mon_db_ods_major, database->dbb_ods_version);
|
|
// minor ODS version
|
|
writer.insertInt(f_mon_db_ods_minor, database->dbb_minor_version);
|
|
// oldest interesting transaction
|
|
writer.insertInt(f_mon_db_oit, database->dbb_oldest_transaction);
|
|
// oldest active transaction
|
|
writer.insertInt(f_mon_db_oat, database->dbb_oldest_active);
|
|
// oldest snapshot transaction
|
|
writer.insertInt(f_mon_db_ost, database->dbb_oldest_snapshot);
|
|
// next transaction
|
|
writer.insertInt(f_mon_db_nt, database->dbb_next_transaction);
|
|
// number of page buffers
|
|
writer.insertInt(f_mon_db_page_bufs, database->dbb_bcb->bcb_count);
|
|
|
|
int temp;
|
|
|
|
// SQL dialect
|
|
temp = (database->dbb_flags & DBB_DB_SQL_dialect_3) ? 3 : 1;
|
|
writer.insertInt(f_mon_db_dialect, temp);
|
|
|
|
// shutdown mode
|
|
if (database->dbb_ast_flags & DBB_shutdown_full)
|
|
temp = shut_mode_full;
|
|
else if (database->dbb_ast_flags & DBB_shutdown_single)
|
|
temp = shut_mode_single;
|
|
else if (database->dbb_ast_flags & DBB_shutdown)
|
|
temp = shut_mode_multi;
|
|
else
|
|
temp = shut_mode_online;
|
|
writer.insertInt(f_mon_db_shut_mode, temp);
|
|
|
|
// sweep interval
|
|
writer.insertInt(f_mon_db_sweep_int, database->dbb_sweep_interval);
|
|
// read only flag
|
|
temp = (database->dbb_flags & DBB_read_only) ? 1 : 0;
|
|
writer.insertInt(f_mon_db_read_only, temp);
|
|
// forced writes flag
|
|
temp = (database->dbb_flags & DBB_force_write) ? 1 : 0;
|
|
writer.insertInt(f_mon_db_forced_writes, temp);
|
|
// reserve space flag
|
|
temp = (database->dbb_flags & DBB_no_reserve) ? 0 : 1;
|
|
writer.insertInt(f_mon_db_res_space, temp);
|
|
// creation date
|
|
writer.insertTimeStamp(f_mon_db_created, database->dbb_creation_date.value());
|
|
// database size
|
|
writer.insertBigInt(f_mon_db_pages, PageSpace::actAlloc(database));
|
|
|
|
// database state
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
{ // scope
|
|
BackupManager::SharedDatabaseHolder sdbHolder(tdbb, database->dbb_backup_manager);
|
|
|
|
switch (database->dbb_backup_manager->get_state())
|
|
{
|
|
case nbak_state_normal:
|
|
temp = backup_state_normal;
|
|
break;
|
|
case nbak_state_stalled:
|
|
temp = backup_state_stalled;
|
|
break;
|
|
case nbak_state_merge:
|
|
temp = backup_state_merge;
|
|
break;
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
}
|
|
writer.insertInt(f_mon_db_backup_state, temp);
|
|
// statistics
|
|
writer.insertBigInt(f_mon_db_stat_id, getGlobalId(stat_id));
|
|
putStatistics(database->dbb_stats, writer, stat_id, stat_database);
|
|
putMemoryUsage(database->dbb_memory_stats, writer, stat_id, stat_database);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::putAttachment(const Attachment* attachment, ClumpletWriter& writer, int stat_id)
|
|
{
|
|
fb_assert(attachment);
|
|
|
|
writer.insertByte(TAG_RECORD, rel_mon_attachments);
|
|
|
|
int temp = mon_state_idle;
|
|
|
|
for (const jrd_tra* transaction_itr = attachment->att_transactions;
|
|
transaction_itr; transaction_itr = transaction_itr->tra_next)
|
|
{
|
|
if (transaction_itr->tra_requests)
|
|
{
|
|
temp = mon_state_active;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// user (MUST BE ALWAYS THE FIRST ITEM PASSED!)
|
|
writer.insertString(f_mon_att_user, attachment->att_user->usr_user_name);
|
|
// attachment id
|
|
writer.insertInt(f_mon_att_id, attachment->att_attachment_id);
|
|
// process id
|
|
writer.insertInt(f_mon_att_server_pid, getpid());
|
|
// state
|
|
writer.insertInt(f_mon_att_state, temp);
|
|
// attachment name
|
|
writer.insertPath(f_mon_att_name, attachment->att_filename);
|
|
// role
|
|
writer.insertString(f_mon_att_role, attachment->att_user->usr_sql_role_name);
|
|
// remote protocol
|
|
writer.insertString(f_mon_att_remote_proto, attachment->att_network_protocol);
|
|
// remote address
|
|
writer.insertString(f_mon_att_remote_addr, attachment->att_remote_address);
|
|
// remote process id
|
|
writer.insertInt(f_mon_att_remote_pid, attachment->att_remote_pid);
|
|
// remote process name
|
|
writer.insertPath(f_mon_att_remote_process, attachment->att_remote_process);
|
|
// charset
|
|
writer.insertInt(f_mon_att_charset_id, attachment->att_charset);
|
|
// timestamp
|
|
writer.insertTimeStamp(f_mon_att_timestamp, attachment->att_timestamp.value());
|
|
// garbage collection flag
|
|
temp = (attachment->att_flags & ATT_no_cleanup) ? 0 : 1;
|
|
writer.insertInt(f_mon_att_gc, temp);
|
|
// statistics
|
|
writer.insertBigInt(f_mon_att_stat_id, getGlobalId(stat_id));
|
|
putStatistics(attachment->att_stats, writer, stat_id, stat_attachment);
|
|
putMemoryUsage(attachment->att_memory_stats, writer, stat_id, stat_attachment);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::putTransaction(const jrd_tra* transaction, ClumpletWriter& writer, int stat_id)
|
|
{
|
|
fb_assert(transaction);
|
|
|
|
writer.insertByte(TAG_RECORD, rel_mon_transactions);
|
|
|
|
int temp;
|
|
|
|
// transaction id
|
|
writer.insertInt(f_mon_tra_id, transaction->tra_number);
|
|
// attachment id
|
|
writer.insertInt(f_mon_tra_att_id, transaction->tra_attachment->att_attachment_id);
|
|
// state
|
|
temp = transaction->tra_requests ? mon_state_active : mon_state_idle;
|
|
writer.insertInt(f_mon_tra_state, temp);
|
|
// timestamp
|
|
writer.insertTimeStamp(f_mon_tra_timestamp, transaction->tra_timestamp.value());
|
|
// top transaction
|
|
writer.insertInt(f_mon_tra_top, transaction->tra_top);
|
|
// oldest transaction
|
|
writer.insertInt(f_mon_tra_oit, transaction->tra_oldest);
|
|
// oldest active transaction
|
|
writer.insertInt(f_mon_tra_oat, transaction->tra_oldest_active);
|
|
// isolation mode
|
|
if (transaction->tra_flags & TRA_degree3)
|
|
temp = iso_mode_consistency;
|
|
else if (transaction->tra_flags & TRA_read_committed)
|
|
{
|
|
temp = (transaction->tra_flags & TRA_rec_version) ?
|
|
iso_mode_rc_version : iso_mode_rc_no_version;
|
|
}
|
|
else
|
|
temp = iso_mode_concurrency;
|
|
writer.insertInt(f_mon_tra_iso_mode, temp);
|
|
// lock timeout
|
|
writer.insertInt(f_mon_tra_lock_timeout,
|
|
transaction->tra_lock_timeout);
|
|
// read only flag
|
|
temp = (transaction->tra_flags & TRA_readonly) ? 1 : 0;
|
|
writer.insertInt(f_mon_tra_read_only, temp);
|
|
// autocommit flag
|
|
temp = (transaction->tra_flags & TRA_autocommit) ? 1 : 0;
|
|
writer.insertInt(f_mon_tra_auto_commit, temp);
|
|
// auto undo flag
|
|
temp = (transaction->tra_flags & TRA_no_auto_undo) ? 0 : 1;
|
|
writer.insertInt(f_mon_tra_auto_undo, temp);
|
|
// statistics
|
|
writer.insertBigInt(f_mon_tra_stat_id, getGlobalId(stat_id));
|
|
putStatistics(transaction->tra_stats, writer, stat_id, stat_transaction);
|
|
putMemoryUsage(transaction->tra_memory_stats, writer, stat_id, stat_transaction);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::putRequest(const jrd_req* request, ClumpletWriter& writer, int stat_id)
|
|
{
|
|
fb_assert(request);
|
|
|
|
writer.insertByte(TAG_RECORD, rel_mon_statements);
|
|
|
|
// request id
|
|
writer.insertBigInt(f_mon_stmt_id, getGlobalId(request->req_id));
|
|
// attachment id
|
|
if (request->req_attachment) {
|
|
writer.insertInt(f_mon_stmt_att_id, request->req_attachment->att_attachment_id);
|
|
}
|
|
else {
|
|
writer.insertInt(f_mon_stmt_att_id, 0);
|
|
}
|
|
// state, transaction ID, timestamp
|
|
if (request->req_flags & req_active) {
|
|
const bool is_stalled = (request->req_flags & req_stall);
|
|
writer.insertInt(f_mon_stmt_state, is_stalled ? mon_state_stalled : mon_state_active);
|
|
const int tra_id = request->req_transaction ? request->req_transaction->tra_number : 0;
|
|
writer.insertInt(f_mon_stmt_tra_id, tra_id);
|
|
writer.insertTimeStamp(f_mon_stmt_timestamp, request->req_timestamp.value());
|
|
}
|
|
else {
|
|
writer.insertInt(f_mon_stmt_state, mon_state_idle);
|
|
writer.insertInt(f_mon_stmt_tra_id, 0);
|
|
ISC_TIMESTAMP empty = {0, 0};
|
|
writer.insertTimeStamp(f_mon_stmt_timestamp, empty);
|
|
}
|
|
// sql text
|
|
writer.insertString(f_mon_stmt_sql_text, request->req_sql_text);
|
|
// statistics
|
|
writer.insertBigInt(f_mon_stmt_stat_id, getGlobalId(stat_id));
|
|
putStatistics(request->req_stats, writer, stat_id, stat_statement);
|
|
putMemoryUsage(request->req_memory_stats, writer, stat_id, stat_statement);
|
|
}
|
|
|
|
|
|
void DatabaseSnapshot::putCall(const jrd_req* request, ClumpletWriter& writer, int stat_id)
|
|
{
|
|
fb_assert(request);
|
|
|
|
const jrd_req* statement = request->req_caller;
|
|
while (statement->req_caller)
|
|
statement = statement->req_caller;
|
|
fb_assert(statement);
|
|
|
|
writer.insertByte(TAG_RECORD, rel_mon_calls);
|
|
|
|
// call id
|
|
writer.insertBigInt(f_mon_call_id, getGlobalId(request->req_id));
|
|
// statement id
|
|
writer.insertBigInt(f_mon_call_stmt_id, getGlobalId(statement->req_id));
|
|
// caller id
|
|
if (statement == request->req_caller) {
|
|
writer.insertBigInt(f_mon_call_caller_id, 0);
|
|
}
|
|
else {
|
|
writer.insertBigInt(f_mon_call_caller_id, getGlobalId(request->req_caller->req_id));
|
|
}
|
|
// object name/type
|
|
if (request->req_procedure) {
|
|
writer.insertString(f_mon_call_name, request->req_procedure->prc_name.c_str());
|
|
writer.insertInt(f_mon_call_type, obj_procedure);
|
|
}
|
|
else if (!request->req_trg_name.isEmpty()) {
|
|
writer.insertString(f_mon_call_name, request->req_trg_name.c_str());
|
|
writer.insertInt(f_mon_call_type, obj_trigger);
|
|
}
|
|
else {
|
|
// we should never be here...
|
|
fb_assert(false);
|
|
// ... but just in case it happened...
|
|
writer.insertString(f_mon_call_name, "");
|
|
writer.insertInt(f_mon_call_type, 0);
|
|
}
|
|
// timestamp
|
|
writer.insertTimeStamp(f_mon_call_timestamp, request->req_timestamp.value());
|
|
// source line/column
|
|
writer.insertInt(f_mon_call_src_line, request->req_src_line);
|
|
writer.insertInt(f_mon_call_src_column, request->req_src_column);
|
|
// statistics
|
|
writer.insertBigInt(f_mon_call_stat_id, getGlobalId(stat_id));
|
|
putStatistics(request->req_stats, writer, stat_id, stat_call);
|
|
putMemoryUsage(request->req_memory_stats, writer, stat_id, stat_call);
|
|
}
|
|
|
|
void DatabaseSnapshot::putStatistics(const RuntimeStatistics& statistics,
|
|
ClumpletWriter& writer,
|
|
int stat_id,
|
|
int stat_group)
|
|
{
|
|
// statistics id
|
|
const SINT64 id = getGlobalId(stat_id);
|
|
|
|
// physical I/O statistics
|
|
writer.insertByte(TAG_RECORD, rel_mon_io_stats);
|
|
writer.insertBigInt(f_mon_io_stat_id, id);
|
|
writer.insertInt(f_mon_io_stat_group, stat_group);
|
|
writer.insertBigInt(f_mon_io_page_reads, statistics.getValue(RuntimeStatistics::PAGE_READS));
|
|
writer.insertBigInt(f_mon_io_page_writes, statistics.getValue(RuntimeStatistics::PAGE_WRITES));
|
|
writer.insertBigInt(f_mon_io_page_fetches, statistics.getValue(RuntimeStatistics::PAGE_FETCHES));
|
|
writer.insertBigInt(f_mon_io_page_marks, statistics.getValue(RuntimeStatistics::PAGE_MARKS));
|
|
|
|
// logical I/O statistics
|
|
writer.insertByte(TAG_RECORD, rel_mon_rec_stats);
|
|
writer.insertBigInt(f_mon_rec_stat_id, id);
|
|
writer.insertInt(f_mon_rec_stat_group, stat_group);
|
|
writer.insertBigInt(f_mon_rec_seq_reads, statistics.getValue(RuntimeStatistics::RECORD_SEQ_READS));
|
|
writer.insertBigInt(f_mon_rec_idx_reads, statistics.getValue(RuntimeStatistics::RECORD_IDX_READS));
|
|
writer.insertBigInt(f_mon_rec_inserts, statistics.getValue(RuntimeStatistics::RECORD_INSERTS));
|
|
writer.insertBigInt(f_mon_rec_updates, statistics.getValue(RuntimeStatistics::RECORD_UPDATES));
|
|
writer.insertBigInt(f_mon_rec_deletes, statistics.getValue(RuntimeStatistics::RECORD_DELETES));
|
|
writer.insertBigInt(f_mon_rec_backouts, statistics.getValue(RuntimeStatistics::RECORD_BACKOUTS));
|
|
writer.insertBigInt(f_mon_rec_purges, statistics.getValue(RuntimeStatistics::RECORD_PURGES));
|
|
writer.insertBigInt(f_mon_rec_expunges, statistics.getValue(RuntimeStatistics::RECORD_EXPUNGES));
|
|
}
|
|
|
|
void DatabaseSnapshot::putContextVars(StringMap& variables,
|
|
ClumpletWriter& writer,
|
|
int object_id, bool is_attachment)
|
|
{
|
|
StringMap::Accessor accessor(&variables);
|
|
|
|
for (bool found = accessor.getFirst(); found; found = accessor.getNext())
|
|
{
|
|
writer.insertByte(TAG_RECORD, rel_mon_ctx_vars);
|
|
|
|
if (is_attachment)
|
|
writer.insertInt(f_mon_ctx_var_att_id, object_id);
|
|
else
|
|
writer.insertInt(f_mon_ctx_var_tra_id, object_id);
|
|
|
|
writer.insertString(f_mon_ctx_var_name, accessor.current()->first);
|
|
writer.insertString(f_mon_ctx_var_value, accessor.current()->second);
|
|
}
|
|
}
|
|
|
|
void DatabaseSnapshot::putMemoryUsage(const MemoryStats& stats,
|
|
ClumpletWriter& writer,
|
|
int stat_id,
|
|
int stat_group)
|
|
{
|
|
// statistics id
|
|
const SINT64 id = getGlobalId(stat_id);
|
|
|
|
// memory usage
|
|
writer.insertByte(TAG_RECORD, rel_mon_mem_usage);
|
|
writer.insertBigInt(f_mon_mem_stat_id, id);
|
|
writer.insertInt(f_mon_mem_stat_group, stat_group);
|
|
writer.insertBigInt(f_mon_mem_cur_used, stats.getCurrentUsage());
|
|
writer.insertBigInt(f_mon_mem_cur_alloc, stats.getCurrentMapping());
|
|
writer.insertBigInt(f_mon_mem_max_used, stats.getMaximumUsage());
|
|
writer.insertBigInt(f_mon_mem_max_alloc, stats.getMaximumMapping());
|
|
}
|