8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-27 06:43:04 +01:00
firebird-mirror/src/jrd/Monitoring.cpp
dimitr 6ae3e2cbaf 1) Fixed CORE-4383: Index and BLOBs garbage collection doesn't work for update_in_place().
2) Applied the same approach to temporary GC records.
3) Refactored the Record class to avoid reallocations, to improve encapsulation and to simplify the code.
4) Slightly refactored UndoItem to isolate its logic from the Record class.
5) Reduced the in-memory size of the undo log.
6) Slightly better alternative to the legacy NULL reporting for outer joins.
7) Minor fixes and adjustments in BufferedStream.
2015-02-19 14:15:00 +00:00

1335 lines
39 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 "../common/classes/fb_string.h"
#include "../common/gdsassert.h"
#include "../jrd/jrd.h"
#include "../jrd/cch.h"
#include "../jrd/ini.h"
#include "../jrd/nbak.h"
#include "../jrd/req.h"
#include "../jrd/tra.h"
#include "../jrd/blb_proto.h"
#include "../common/isc_proto.h"
#include "../common/isc_f_proto.h"
#include "../common/isc_s_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/opt_proto.h"
#include "../jrd/pag_proto.h"
#include "../jrd/CryptoManager.h"
#include "../jrd/Relation.h"
#include "../jrd/RecordBuffer.h"
#include "../jrd/Monitoring.h"
#include "../jrd/Function.h"
#ifdef WIN_NT
#include <process.h>
#define getpid _getpid
#endif
const char* const SCRATCH = "fb_monitor_";
using namespace Firebird;
using namespace Jrd;
const Format* MonitoringTableScan::getFormat(thread_db* tdbb, jrd_rel* relation) const
{
MonitoringSnapshot* const snapshot = MonitoringSnapshot::create(tdbb);
return snapshot->getData(relation)->getFormat();
}
bool MonitoringTableScan::retrieveRecord(thread_db* tdbb, jrd_rel* relation,
FB_UINT64 position, Record* record) const
{
MonitoringSnapshot* const snapshot = MonitoringSnapshot::create(tdbb);
return snapshot->getData(relation)->fetch(position, record);
}
// MonitoringData class
MonitoringData::MonitoringData(const Database* dbb)
{
string name;
name.printf(MONITOR_FILE, dbb->getUniqueFileId().c_str());
Arg::StatusVector statusVector;
try
{
shared_memory.reset(FB_NEW(*dbb->dbb_permanent)
SharedMemory<MonitoringHeader>(name.c_str(), DEFAULT_SIZE, this));
}
catch (const Exception& ex)
{
iscLogException("MonitoringData: Cannot initialize the shared memory region", ex);
throw;
}
fb_assert(shared_memory->getHeader()->mhb_header_version == MemoryHeader::HEADER_VERSION);
fb_assert(shared_memory->getHeader()->mhb_version == MONITOR_VERSION);
}
MonitoringData::~MonitoringData()
{
Guard guard(this);
if (shared_memory->getHeader()->used == sizeof(Header))
shared_memory->removeMapFile();
}
void MonitoringData::acquire()
{
shared_memory->mutexLock();
if (shared_memory->getHeader()->allocated > shared_memory->sh_mem_length_mapped)
{
#ifdef HAVE_OBJECT_MAP
Arg::StatusVector statusVector;
shared_memory->remapFile(statusVector, shared_memory->getHeader()->allocated, false);
if (!shared_memory->remapFile(statusVector, shared_memory->getHeader()->allocated, false))
{
status_exception::raise(statusVector);
}
#else
status_exception::raise(Arg::Gds(isc_montabexh));
#endif
}
}
void MonitoringData::release()
{
shared_memory->mutexUnlock();
}
void MonitoringData::read(SLONG att_id, TempSpace& temp)
{
offset_t position = 0;
// First, find and copy data for the given session
for (ULONG offset = alignOffset(sizeof(Header)); offset < shared_memory->getHeader()->used;)
{
UCHAR* const ptr = (UCHAR*) shared_memory->getHeader() + offset;
const Element* const element = (Element*) ptr;
const ULONG length = alignOffset(sizeof(Element) + element->length);
if (element->attId == att_id)
{
fb_assert(shared_memory->getHeader()->used >= offset + length);
temp.write(position, ptr + sizeof(Element), element->length);
position += element->length;
}
offset += length;
}
// Second, copy data of other sessions
for (ULONG offset = alignOffset(sizeof(Header)); offset < shared_memory->getHeader()->used;)
{
UCHAR* const ptr = (UCHAR*) shared_memory->getHeader() + offset;
const Element* const element = (Element*) ptr;
const ULONG length = alignOffset(sizeof(Element) + element->length);
if (element->attId != att_id)
{
fb_assert(shared_memory->getHeader()->used >= offset + length);
temp.write(position, ptr + sizeof(Element), element->length);
position += element->length;
}
offset += length;
}
}
ULONG MonitoringData::setup(SLONG att_id)
{
ensureSpace(sizeof(Element));
// Prepare for writing new data at the tail
const ULONG offset = shared_memory->getHeader()->used;
UCHAR* const ptr = (UCHAR*) shared_memory->getHeader() + offset;
Element* const element = (Element*) ptr;
element->attId = att_id;
element->length = 0;
shared_memory->getHeader()->used += alignOffset(sizeof(Element));
return offset;
}
void MonitoringData::write(ULONG offset, ULONG length, const void* buffer)
{
ensureSpace(length);
// Write data item at the tail
UCHAR* const ptr = (UCHAR*) shared_memory->getHeader() + offset;
Element* const element = (Element*) ptr;
memcpy(ptr + sizeof(Element) + element->length, buffer, length);
ULONG previous = alignOffset(sizeof(Element) + element->length);
element->length += length;
ULONG current = alignOffset(sizeof(Element) + element->length);
shared_memory->getHeader()->used += (current - previous);
}
void MonitoringData::cleanup(SLONG att_id)
{
// Remove information about the given session
for (ULONG offset = alignOffset(sizeof(Header)); offset < shared_memory->getHeader()->used;)
{
UCHAR* const ptr = (UCHAR*) shared_memory->getHeader() + offset;
const Element* const element = (Element*) ptr;
const ULONG length = alignOffset(sizeof(Element) + element->length);
if (element->attId == att_id)
{
fb_assert(shared_memory->getHeader()->used >= offset + length);
memmove(ptr, ptr + length, shared_memory->getHeader()->used - offset - length);
shared_memory->getHeader()->used -= length;
}
else
{
offset += length;
}
}
}
void MonitoringData::enumerate(SessionList& sessions)
{
// Return IDs for all known sessions
for (ULONG offset = alignOffset(sizeof(Header)); offset < shared_memory->getHeader()->used;)
{
UCHAR* const ptr = (UCHAR*) shared_memory->getHeader() + offset;
const Element* const element = (Element*) ptr;
const ULONG length = alignOffset(sizeof(Element) + element->length);
offset += length;
sessions.add(element->attId);
}
}
void MonitoringData::ensureSpace(ULONG length)
{
ULONG newSize = shared_memory->getHeader()->used + length;
if (newSize > shared_memory->getHeader()->allocated)
{
newSize = FB_ALIGN(newSize, DEFAULT_SIZE);
#ifdef HAVE_OBJECT_MAP
Arg::StatusVector statusVector;
if (!shared_memory->remapFile(statusVector, newSize, true))
{
status_exception::raise(statusVector);
}
shared_memory->getHeader()->allocated = shared_memory->sh_mem_length_mapped;
#else
status_exception::raise(Arg::Gds(isc_montabexh));
#endif
}
}
void MonitoringData::mutexBug(int osErrorCode, const char* s)
{
string msg;
msg.printf("MONITOR: mutex %s error, status = %d", s, osErrorCode);
fb_utils::logAndDie(msg.c_str());
}
bool MonitoringData::initialize(SharedMemoryBase* sm, bool initialize)
{
if (initialize)
{
MonitoringHeader* header = reinterpret_cast<MonitoringHeader*>(sm->sh_mem_header);
// Initialize the shared data header
header->init(SharedMemoryBase::SRAM_DATABASE_SNAPSHOT, MONITOR_VERSION);
header->used = alignOffset(sizeof(Header));
header->allocated = sm->sh_mem_length_mapped;
}
return true;
}
ULONG MonitoringData::alignOffset(ULONG unaligned)
{
return (ULONG) Firebird::MEM_ALIGN(unaligned);
}
// MonitoringSnapshot class
MonitoringSnapshot* MonitoringSnapshot::create(thread_db* tdbb)
{
SET_TDBB(tdbb);
jrd_tra* transaction = tdbb->getTransaction();
fb_assert(transaction);
if (!transaction->tra_mon_snapshot)
{
// Create a database snapshot and store it
// in the transaction block
MemoryPool& pool = *transaction->tra_pool;
transaction->tra_mon_snapshot = FB_NEW(pool) MonitoringSnapshot(tdbb, pool);
}
return transaction->tra_mon_snapshot;
}
MonitoringSnapshot::MonitoringSnapshot(thread_db* tdbb, MemoryPool& pool)
: SnapshotData(pool)
{
SET_TDBB(tdbb);
PAG_header(tdbb, true);
Database* const dbb = tdbb->getDatabase();
fb_assert(dbb);
Attachment* const attachment = tdbb->getAttachment();
fb_assert(attachment);
const SLONG self_att_id = attachment->att_attachment_id;
// Initialize record buffers
RecordBuffer* const dbb_buffer = allocBuffer(tdbb, pool, rel_mon_database);
RecordBuffer* const att_buffer = allocBuffer(tdbb, pool, rel_mon_attachments);
RecordBuffer* const tra_buffer = allocBuffer(tdbb, pool, rel_mon_transactions);
RecordBuffer* const stmt_buffer = allocBuffer(tdbb, pool, rel_mon_statements);
RecordBuffer* const call_buffer = allocBuffer(tdbb, pool, rel_mon_calls);
RecordBuffer* const io_stat_buffer = allocBuffer(tdbb, pool, rel_mon_io_stats);
RecordBuffer* const rec_stat_buffer = allocBuffer(tdbb, pool, rel_mon_rec_stats);
RecordBuffer* const ctx_var_buffer = allocBuffer(tdbb, pool, rel_mon_ctx_vars);
RecordBuffer* const mem_usage_buffer = allocBuffer(tdbb, pool, rel_mon_mem_usage);
RecordBuffer* const tab_stat_buffer = allocBuffer(tdbb, pool, rel_mon_tab_stats);
// Dump our own data and downgrade the lock, if required
Monitoring::dumpAttachment(tdbb, attachment, false);
if (!(attachment->att_flags & ATT_monitor_done))
{
LCK_convert(tdbb, attachment->att_monitor_lock, LCK_SR, LCK_NO_WAIT);
attachment->att_flags |= ATT_monitor_done;
}
// Enumerate active sessions
MonitoringData::SessionList sessions(pool);
Lock temp_lock(tdbb, sizeof(SLONG), LCK_monitor), *lock = &temp_lock;
{ // scope for the guard
MonitoringData::Guard guard(dbb->dbb_monitoring_data);
dbb->dbb_monitoring_data->enumerate(sessions);
}
// Signal other sessions to dump their state
{ // scope for the temporary status
ThreadStatusGuard temp_status(tdbb);
for (SLONG* iter = sessions.begin(); iter != sessions.end(); iter++)
{
if (*iter != self_att_id)
{
lock->lck_key.lck_long = *iter;
if (LCK_lock(tdbb, lock, LCK_SR, LCK_WAIT))
LCK_release(tdbb, lock);
}
}
}
// Read the dump into a temporary space. While being there,
// also check for dead sessions and garbage collect them.
TempSpace temp_space(pool, SCRATCH);
{ // scope for the guard
MonitoringData::Guard guard(dbb->dbb_monitoring_data);
ThreadStatusGuard temp_status(tdbb);
lock->lck_type = LCK_attachment;
for (SLONG* iter = sessions.begin(); iter != sessions.end(); iter++)
{
if (*iter != self_att_id)
{
lock->lck_key.lck_long = *iter;
if (LCK_lock(tdbb, lock, LCK_EX, LCK_NO_WAIT))
{
LCK_release(tdbb, lock);
dbb->dbb_monitoring_data->cleanup(*iter);
}
}
}
dbb->dbb_monitoring_data->read(self_att_id, temp_space);
}
string databaseName(dbb->dbb_database_name.c_str());
ISC_systemToUtf8(databaseName);
const string& userName = attachment->att_user->usr_user_name;
const bool locksmith = attachment->locksmith();
// Parse the dump
MonitoringData::Reader reader(pool, temp_space);
RecordBuffer* buffer = NULL;
Record* record = NULL;
bool dbb_processed = false, fields_processed = false;
bool dbb_allowed = false, att_allowed = false;
SnapshotData::DumpRecord dumpRecord(pool);
while (reader.getRecord(dumpRecord))
{
const int rid = dumpRecord.getRelationId();
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;
case rel_mon_tab_stats:
buffer = tab_stat_buffer;
break;
default:
fb_assert(false);
}
if (buffer)
{
record = buffer->getTempRecord();
record->nullify();
}
else
{
record = NULL;
}
SnapshotData::DumpField dumpField;
while (dumpRecord.getField(dumpField))
{
const USHORT fid = dumpField.id;
const FB_SIZE_T length = dumpField.length;
const char* source = (const char*) dumpField.data;
// All the strings that may require transliteration (i.e. the target charset is not NONE)
// are known to be in the metadata charset or ASCII (which is binary compatible).
const int charset = ttype_metadata;
// 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(tdbb, record, dumpField, charset);
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(tdbb, record, dumpField, charset);
fields_processed = true;
dbb_processed = true;
}
}
// generic logic that covers all other relations
else if (record && dbb_allowed && att_allowed)
{
putField(tdbb, record, dumpField, charset);
fields_processed = true;
dbb_processed = true;
}
}
if (fields_processed)
{
buffer->store(record);
fields_processed = false;
}
}
}
void SnapshotData::clearSnapshot()
{
for (FB_SIZE_T i = 0; i < m_snapshot.getCount(); i++)
delete m_snapshot[i].data;
m_snapshot.clear();
}
RecordBuffer* SnapshotData::getData(const jrd_rel* relation) const
{
fb_assert(relation);
return getData(relation->rel_id);
}
RecordBuffer* SnapshotData::getData(int id) const
{
for (FB_SIZE_T i = 0; i < m_snapshot.getCount(); i++)
{
if (m_snapshot[i].rel_id == id)
return m_snapshot[i].data;
}
return NULL;
}
RecordBuffer* SnapshotData::allocBuffer(thread_db* tdbb, MemoryPool& pool, int rel_id)
{
jrd_rel* const relation = MET_lookup_relation_id(tdbb, rel_id, false);
fb_assert(relation);
MET_scan_relation(tdbb, relation);
fb_assert(relation->isVirtual());
const Format* const format = MET_current(tdbb, relation);
fb_assert(format);
RecordBuffer* const buffer = FB_NEW(pool) RecordBuffer(pool, format);
const RelationData data = {relation->rel_id, buffer};
m_snapshot.add(data);
return buffer;
}
void SnapshotData::putField(thread_db* tdbb, Record* record, const DumpField& field, int charset)
{
fb_assert(record);
const Format* const format = record->getFormat();
fb_assert(format);
dsc to_desc;
if (field.id < format->fmt_count)
to_desc = format->fmt_desc[field.id];
if (to_desc.isUnknown())
return;
to_desc.dsc_address += (IPTR) record->getData();
if (field.type == VALUE_GLOBAL_ID)
{
// special case: translate 64-bit global ID into 32-bit local ID
fb_assert(field.length == sizeof(SINT64));
SINT64 global_id;
memcpy(&global_id, field.data, field.length);
SLONG local_id;
if (!m_map.get(global_id, local_id))
{
local_id = ++m_counter;
m_map.put(global_id, local_id);
}
dsc from_desc;
from_desc.makeLong(0, &local_id);
MOV_move(tdbb, &from_desc, &to_desc);
}
else if (field.type == VALUE_TABLE_ID)
{
// special case: translate relation ID into name
fb_assert(field.length == sizeof(SLONG));
SLONG rel_id;
memcpy(&rel_id, field.data, field.length);
const jrd_rel* const relation = MET_lookup_relation_id(tdbb, rel_id, false);
if (!relation || relation->rel_name.isEmpty())
return;
const MetaName& name = relation->rel_name;
dsc from_desc;
from_desc.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
MOV_move(tdbb, &from_desc, &to_desc);
}
else if (field.type == VALUE_INTEGER)
{
fb_assert(field.length == sizeof(SINT64));
SINT64 value;
memcpy(&value, field.data, field.length);
dsc from_desc;
from_desc.makeInt64(0, &value);
MOV_move(tdbb, &from_desc, &to_desc);
}
else if (field.type == VALUE_TIMESTAMP)
{
fb_assert(field.length == sizeof(ISC_TIMESTAMP));
ISC_TIMESTAMP value;
memcpy(&value, field.data, field.length);
dsc from_desc;
from_desc.makeTimestamp(&value);
MOV_move(tdbb, &from_desc, &to_desc);
}
else if (field.type == VALUE_STRING)
{
dsc from_desc;
MoveBuffer buffer;
if (charset == CS_NONE && to_desc.getCharSet() == CS_METADATA)
{
// ASF: If an attachment using NONE charset has a string using non-ASCII characters,
// nobody will be able to select them in a system field. So we change these characters to
// question marks here - CORE-2602.
UCHAR* p = buffer.getBuffer(field.length);
const UCHAR* s = (const UCHAR*) field.data;
for (const UCHAR* end = buffer.end(); p < end; ++p, ++s)
*p = (*s > 0x7F ? '?' : *s);
from_desc.makeText(field.length, CS_ASCII, buffer.begin());
}
else
from_desc.makeText(field.length, charset, (UCHAR*) field.data);
MOV_move(tdbb, &from_desc, &to_desc);
}
else if (field.type == VALUE_BOOLEAN)
{
fb_assert(field.length == sizeof(UCHAR));
UCHAR value;
memcpy(&value, field.data, field.length);
dsc from_desc;
from_desc.makeBoolean(&value);
MOV_move(tdbb, &from_desc, &to_desc);
}
else
{
fb_assert(false);
}
// hvlad: detach just created temporary blob from request to bound its
// lifetime to transaction. This is necessary as this blob belongs to
// the MON$ table and must be accessible until transaction ends.
if (to_desc.isBlob())
{
bid* blob_id = reinterpret_cast<bid*>(to_desc.dsc_address);
jrd_tra* tran = tdbb->getTransaction();
if (!tran->tra_blobs->locate(blob_id->bid_temp_id()))
fb_assert(false);
BlobIndex& blobIdx = tran->tra_blobs->current();
fb_assert(!blobIdx.bli_materialized);
if (blobIdx.bli_request)
{
if (!blobIdx.bli_request->req_blobs.locate(blobIdx.bli_temp_id))
fb_assert(false);
blobIdx.bli_request->req_blobs.fastRemove();
blobIdx.bli_request = NULL;
}
}
record->clearNull(field.id);
}
// Monitoring class
SINT64 Monitoring::getGlobalId(int value)
{
return ((SINT64) getpid() << BITS_PER_LONG) + value;
}
void Monitoring::putDatabase(SnapshotData::DumpRecord& record, const Database* database,
MonitoringData::Writer& writer, int stat_id, int backup_state)
{
fb_assert(database);
record.reset(rel_mon_database);
PathName databaseName(database->dbb_database_name);
ISC_systemToUtf8(databaseName);
// database name or alias (MUST BE ALWAYS THE FIRST ITEM PASSED!)
record.storeString(f_mon_db_name, databaseName);
// page size
record.storeInteger(f_mon_db_page_size, database->dbb_page_size);
// major ODS version
record.storeInteger(f_mon_db_ods_major, database->dbb_ods_version);
// minor ODS version
record.storeInteger(f_mon_db_ods_minor, database->dbb_minor_version);
// oldest interesting transaction
record.storeInteger(f_mon_db_oit, database->dbb_oldest_transaction);
// oldest active transaction
record.storeInteger(f_mon_db_oat, database->dbb_oldest_active);
// oldest snapshot transaction
record.storeInteger(f_mon_db_ost, database->dbb_oldest_snapshot);
// next transaction
record.storeInteger(f_mon_db_nt, database->dbb_next_transaction);
// number of page buffers
record.storeInteger(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;
record.storeInteger(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;
record.storeInteger(f_mon_db_shut_mode, temp);
// sweep interval
record.storeInteger(f_mon_db_sweep_int, database->dbb_sweep_interval);
// read only flag
temp = database->readOnly() ? 1 : 0;
record.storeInteger(f_mon_db_read_only, temp);
// forced writes flag
temp = (database->dbb_flags & DBB_force_write) ? 1 : 0;
record.storeInteger(f_mon_db_forced_writes, temp);
// reserve space flag
temp = (database->dbb_flags & DBB_no_reserve) ? 0 : 1;
record.storeInteger(f_mon_db_res_space, temp);
// creation date
record.storeTimestamp(f_mon_db_created, database->dbb_creation_date);
// database size
record.storeInteger(f_mon_db_pages, PageSpace::actAlloc(database));
// database backup state
record.storeInteger(f_mon_db_backup_state, backup_state);
// crypt thread status
if (database->dbb_crypto_manager)
record.storeInteger(f_mon_db_crypt_page, database->dbb_crypto_manager->getCurrentPage());
// database owner
record.storeString(f_mon_db_owner, database->dbb_owner);
// statistics
record.storeGlobalId(f_mon_db_stat_id, getGlobalId(stat_id));
writer.putRecord(record);
if (database->dbb_flags & DBB_shared)
{
putStatistics(record, database->dbb_stats, writer, stat_id, stat_database);
putMemoryUsage(record, database->dbb_memory_stats, writer, stat_id, stat_database);
}
else
{
RuntimeStatistics zero_rt_stats;
MemoryStats zero_mem_stats;
putStatistics(record, zero_rt_stats, writer, stat_id, stat_database);
putMemoryUsage(record, zero_mem_stats, writer, stat_id, stat_database);
}
}
void Monitoring::putAttachment(SnapshotData::DumpRecord& record, const Jrd::Attachment* attachment,
MonitoringData::Writer& writer, int stat_id)
{
fb_assert(attachment && attachment->att_user);
record.reset(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;
}
}
PathName attName(attachment->att_filename);
ISC_systemToUtf8(attName);
// user (MUST BE ALWAYS THE FIRST ITEM PASSED!)
record.storeString(f_mon_att_user, attachment->att_user->usr_user_name);
// attachment id
record.storeInteger(f_mon_att_id, attachment->att_attachment_id);
// process id
record.storeInteger(f_mon_att_server_pid, getpid());
// state
record.storeInteger(f_mon_att_state, temp);
// attachment name
record.storeString(f_mon_att_name, attName);
// role
record.storeString(f_mon_att_role, attachment->att_user->usr_sql_role_name);
// remote protocol
record.storeString(f_mon_att_remote_proto, attachment->att_network_protocol);
// remote address
record.storeString(f_mon_att_remote_addr, attachment->att_remote_address);
// remote process id
if (attachment->att_remote_pid)
{
record.storeInteger(f_mon_att_remote_pid, attachment->att_remote_pid);
}
// remote process name
record.storeString(f_mon_att_remote_process, attachment->att_remote_process);
// charset
record.storeInteger(f_mon_att_charset_id, attachment->att_charset);
// timestamp
record.storeTimestamp(f_mon_att_timestamp, attachment->att_timestamp);
// garbage collection flag
temp = (attachment->att_flags & ATT_no_cleanup) ? 0 : 1;
record.storeInteger(f_mon_att_gc, temp);
// client library version
record.storeString(f_mon_att_client_version, attachment->att_client_version);
// remote protocol version
record.storeString(f_mon_att_remote_version, attachment->att_remote_protocol);
// remote host name
record.storeString(f_mon_att_remote_host, attachment->att_remote_host);
// OS user name
record.storeString(f_mon_att_remote_os_user, attachment->att_remote_os_user);
// authentication method
record.storeString(f_mon_att_auth_method, attachment->att_user->usr_auth_method);
// statistics
record.storeGlobalId(f_mon_att_stat_id, getGlobalId(stat_id));
// system flag
temp = (attachment->att_flags & ATT_system) ? 1 : 0;
record.storeInteger(f_mon_att_sys_flag, temp);
writer.putRecord(record);
if (attachment->att_database->dbb_flags & DBB_shared)
{
putStatistics(record, attachment->att_stats, writer, stat_id, stat_attachment);
putMemoryUsage(record, attachment->att_memory_stats, writer, stat_id, stat_attachment);
}
else
{
putStatistics(record, attachment->att_database->dbb_stats, writer, stat_id, stat_attachment);
putMemoryUsage(record, attachment->att_database->dbb_memory_stats, writer, stat_id, stat_attachment);
}
// context vars
putContextVars(record, attachment->att_context_vars, writer, attachment->att_attachment_id, true);
}
void Monitoring::putTransaction(SnapshotData::DumpRecord& record, const jrd_tra* transaction,
MonitoringData::Writer& writer, int stat_id)
{
fb_assert(transaction);
record.reset(rel_mon_transactions);
int temp;
// transaction id
record.storeInteger(f_mon_tra_id, transaction->tra_number);
// attachment id
record.storeInteger(f_mon_tra_att_id, transaction->tra_attachment->att_attachment_id);
// state
temp = transaction->tra_requests ? mon_state_active : mon_state_idle;
record.storeInteger(f_mon_tra_state, temp);
// timestamp
record.storeTimestamp(f_mon_tra_timestamp, transaction->tra_timestamp);
// top transaction
record.storeInteger(f_mon_tra_top, transaction->tra_top);
// oldest transaction
record.storeInteger(f_mon_tra_oit, transaction->tra_oldest);
// oldest active transaction
record.storeInteger(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;
}
record.storeInteger(f_mon_tra_iso_mode, temp);
// lock timeout
record.storeInteger(f_mon_tra_lock_timeout, transaction->tra_lock_timeout);
// read only flag
temp = (transaction->tra_flags & TRA_readonly) ? 1 : 0;
record.storeInteger(f_mon_tra_read_only, temp);
// autocommit flag
temp = (transaction->tra_flags & TRA_autocommit) ? 1 : 0;
record.storeInteger(f_mon_tra_auto_commit, temp);
// auto undo flag
temp = (transaction->tra_flags & TRA_no_auto_undo) ? 0 : 1;
record.storeInteger(f_mon_tra_auto_undo, temp);
// statistics
record.storeGlobalId(f_mon_tra_stat_id, getGlobalId(stat_id));
writer.putRecord(record);
putStatistics(record, transaction->tra_stats, writer, stat_id, stat_transaction);
putMemoryUsage(record, transaction->tra_memory_stats, writer, stat_id, stat_transaction);
// context vars
putContextVars(record, transaction->tra_context_vars, writer, transaction->tra_number, false);
}
void Monitoring::putRequest(SnapshotData::DumpRecord& record, const jrd_req* request,
MonitoringData::Writer& writer, int stat_id, const string& plan)
{
fb_assert(request);
record.reset(rel_mon_statements);
// request id
record.storeInteger(f_mon_stmt_id, request->req_id);
// attachment id
if (request->req_attachment)
{
record.storeInteger(f_mon_stmt_att_id, request->req_attachment->att_attachment_id);
}
// state, transaction ID, timestamp
if (request->req_flags & req_active)
{
const bool is_stalled = (request->req_flags & req_stall);
record.storeInteger(f_mon_stmt_state, is_stalled ? mon_state_stalled : mon_state_active);
if (request->req_transaction)
{
record.storeInteger(f_mon_stmt_tra_id, request->req_transaction->tra_number);
}
record.storeTimestamp(f_mon_stmt_timestamp, request->req_timestamp);
}
else
{
record.storeInteger(f_mon_stmt_state, mon_state_idle);
}
const JrdStatement* const statement = request->getStatement();
// sql text
if (statement->sqlText)
record.storeString(f_mon_stmt_sql_text, *statement->sqlText);
// explained plan
if (plan.hasData())
record.storeString(f_mon_stmt_expl_plan, plan);
// statistics
record.storeGlobalId(f_mon_stmt_stat_id, getGlobalId(stat_id));
writer.putRecord(record);
putStatistics(record, request->req_stats, writer, stat_id, stat_statement);
putMemoryUsage(record, request->req_memory_stats, writer, stat_id, stat_statement);
}
void Monitoring::putCall(SnapshotData::DumpRecord& record, const jrd_req* request,
MonitoringData::Writer& writer, int stat_id)
{
fb_assert(request);
const jrd_req* initialRequest = request->req_caller;
while (initialRequest->req_caller)
{
initialRequest = initialRequest->req_caller;
}
fb_assert(initialRequest);
record.reset(rel_mon_calls);
// call id
record.storeInteger(f_mon_call_id, request->req_id);
// statement id
record.storeInteger(f_mon_call_stmt_id, initialRequest->req_id);
// caller id
if (initialRequest != request->req_caller)
{
record.storeInteger(f_mon_call_caller_id, request->req_caller->req_id);
}
const JrdStatement* statement = request->getStatement();
const Routine* routine = statement->getRoutine();
// object name/type
if (routine)
{
if (routine->getName().package.hasData())
record.storeString(f_mon_call_pkg_name, routine->getName().package);
record.storeString(f_mon_call_name, routine->getName().identifier);
record.storeInteger(f_mon_call_type, routine->getObjectType());
}
else if (!statement->triggerName.isEmpty())
{
record.storeString(f_mon_call_name, statement->triggerName);
record.storeInteger(f_mon_call_type, obj_trigger);
}
else
{
// we should never be here...
fb_assert(false);
}
// timestamp
record.storeTimestamp(f_mon_call_timestamp, request->req_timestamp);
// source line/column
if (request->req_src_line)
{
record.storeInteger(f_mon_call_src_line, request->req_src_line);
record.storeInteger(f_mon_call_src_column, request->req_src_column);
}
// statistics
record.storeGlobalId(f_mon_call_stat_id, getGlobalId(stat_id));
writer.putRecord(record);
putStatistics(record, request->req_stats, writer, stat_id, stat_call);
putMemoryUsage(record, request->req_memory_stats, writer, stat_id, stat_call);
}
void Monitoring::putStatistics(SnapshotData::DumpRecord& record, const RuntimeStatistics& statistics,
MonitoringData::Writer& writer, int stat_id, int stat_group)
{
// statistics id
const SINT64 id = getGlobalId(stat_id);
// physical I/O statistics
record.reset(rel_mon_io_stats);
record.storeGlobalId(f_mon_io_stat_id, id);
record.storeInteger(f_mon_io_stat_group, stat_group);
record.storeInteger(f_mon_io_page_reads, statistics.getValue(RuntimeStatistics::PAGE_READS));
record.storeInteger(f_mon_io_page_writes, statistics.getValue(RuntimeStatistics::PAGE_WRITES));
record.storeInteger(f_mon_io_page_fetches, statistics.getValue(RuntimeStatistics::PAGE_FETCHES));
record.storeInteger(f_mon_io_page_marks, statistics.getValue(RuntimeStatistics::PAGE_MARKS));
writer.putRecord(record);
// logical I/O statistics (global)
record.reset(rel_mon_rec_stats);
record.storeGlobalId(f_mon_rec_stat_id, id);
record.storeInteger(f_mon_rec_stat_group, stat_group);
record.storeInteger(f_mon_rec_seq_reads, statistics.getValue(RuntimeStatistics::RECORD_SEQ_READS));
record.storeInteger(f_mon_rec_idx_reads, statistics.getValue(RuntimeStatistics::RECORD_IDX_READS));
record.storeInteger(f_mon_rec_inserts, statistics.getValue(RuntimeStatistics::RECORD_INSERTS));
record.storeInteger(f_mon_rec_updates, statistics.getValue(RuntimeStatistics::RECORD_UPDATES));
record.storeInteger(f_mon_rec_deletes, statistics.getValue(RuntimeStatistics::RECORD_DELETES));
record.storeInteger(f_mon_rec_backouts, statistics.getValue(RuntimeStatistics::RECORD_BACKOUTS));
record.storeInteger(f_mon_rec_purges, statistics.getValue(RuntimeStatistics::RECORD_PURGES));
record.storeInteger(f_mon_rec_expunges, statistics.getValue(RuntimeStatistics::RECORD_EXPUNGES));
record.storeInteger(f_mon_rec_locks, statistics.getValue(RuntimeStatistics::RECORD_LOCKS));
record.storeInteger(f_mon_rec_waits, statistics.getValue(RuntimeStatistics::RECORD_WAITS));
record.storeInteger(f_mon_rec_conflicts, statistics.getValue(RuntimeStatistics::RECORD_CONFLICTS));
record.storeInteger(f_mon_rec_bkver_reads, statistics.getValue(RuntimeStatistics::RECORD_BACKVERSION_READS));
record.storeInteger(f_mon_rec_frg_reads, statistics.getValue(RuntimeStatistics::RECORD_FRAGMENT_READS));
record.storeInteger(f_mon_rec_rpt_reads, statistics.getValue(RuntimeStatistics::RECORD_RPT_READS));
writer.putRecord(record);
// logical I/O statistics (table wise)
for (RuntimeStatistics::Iterator iter = statistics.begin(); iter != statistics.end(); ++iter)
{
const SINT64 rec_stat_id = getGlobalId(fb_utils::genUniqueId());
record.reset(rel_mon_tab_stats);
record.storeGlobalId(f_mon_tab_stat_id, id);
record.storeInteger(f_mon_tab_stat_group, stat_group);
record.storeTableId(f_mon_tab_name, (*iter).getRelationId());
record.storeGlobalId(f_mon_tab_rec_stat_id, rec_stat_id);
writer.putRecord(record);
record.reset(rel_mon_rec_stats);
record.storeGlobalId(f_mon_rec_stat_id, rec_stat_id);
record.storeInteger(f_mon_rec_stat_group, stat_group);
record.storeInteger(f_mon_rec_seq_reads, (*iter).getCounter(RuntimeStatistics::RECORD_SEQ_READS));
record.storeInteger(f_mon_rec_idx_reads, (*iter).getCounter(RuntimeStatistics::RECORD_IDX_READS));
record.storeInteger(f_mon_rec_inserts, (*iter).getCounter(RuntimeStatistics::RECORD_INSERTS));
record.storeInteger(f_mon_rec_updates, (*iter).getCounter(RuntimeStatistics::RECORD_UPDATES));
record.storeInteger(f_mon_rec_deletes, (*iter).getCounter(RuntimeStatistics::RECORD_DELETES));
record.storeInteger(f_mon_rec_backouts, (*iter).getCounter(RuntimeStatistics::RECORD_BACKOUTS));
record.storeInteger(f_mon_rec_purges, (*iter).getCounter(RuntimeStatistics::RECORD_PURGES));
record.storeInteger(f_mon_rec_expunges, (*iter).getCounter(RuntimeStatistics::RECORD_EXPUNGES));
record.storeInteger(f_mon_rec_locks, (*iter).getCounter(RuntimeStatistics::RECORD_LOCKS));
record.storeInteger(f_mon_rec_waits, (*iter).getCounter(RuntimeStatistics::RECORD_WAITS));
record.storeInteger(f_mon_rec_conflicts, (*iter).getCounter(RuntimeStatistics::RECORD_CONFLICTS));
record.storeInteger(f_mon_rec_bkver_reads, (*iter).getCounter(RuntimeStatistics::RECORD_BACKVERSION_READS));
record.storeInteger(f_mon_rec_frg_reads, (*iter).getCounter(RuntimeStatistics::RECORD_FRAGMENT_READS));
record.storeInteger(f_mon_rec_rpt_reads, (*iter).getCounter(RuntimeStatistics::RECORD_RPT_READS));
writer.putRecord(record);
}
}
void Monitoring::putContextVars(SnapshotData::DumpRecord& record, const StringMap& variables,
MonitoringData::Writer& writer, int object_id, bool is_attachment)
{
StringMap::ConstAccessor accessor(&variables);
for (bool found = accessor.getFirst(); found; found = accessor.getNext())
{
record.reset(rel_mon_ctx_vars);
if (is_attachment)
record.storeInteger(f_mon_ctx_var_att_id, object_id);
else
record.storeInteger(f_mon_ctx_var_tra_id, object_id);
record.storeString(f_mon_ctx_var_name, accessor.current()->first);
record.storeString(f_mon_ctx_var_value, accessor.current()->second);
writer.putRecord(record);
}
}
void Monitoring::putMemoryUsage(SnapshotData::DumpRecord& record, const MemoryStats& stats,
MonitoringData::Writer& writer, int stat_id, int stat_group)
{
// statistics id
const SINT64 id = getGlobalId(stat_id);
// memory usage
record.reset(rel_mon_mem_usage);
record.storeGlobalId(f_mon_mem_stat_id, id);
record.storeInteger(f_mon_mem_stat_group, stat_group);
record.storeInteger(f_mon_mem_cur_used, stats.getCurrentUsage());
record.storeInteger(f_mon_mem_cur_alloc, stats.getCurrentMapping());
record.storeInteger(f_mon_mem_max_used, stats.getMaximumUsage());
record.storeInteger(f_mon_mem_max_alloc, stats.getMaximumMapping());
writer.putRecord(record);
}
void Monitoring::checkState(thread_db* tdbb)
{
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
if (attachment->att_flags & ATT_monitor_done)
{
// Enable signal handler for the monitoring stuff
attachment->att_flags &= ~ATT_monitor_done;
LCK_convert(tdbb, attachment->att_monitor_lock, LCK_EX, LCK_WAIT);
}
}
void Monitoring::dumpAttachment(thread_db* tdbb, const Attachment* attachment, bool ast)
{
if (!attachment->att_user)
return;
Database* const dbb = tdbb->getDatabase();
MemoryPool& pool = *dbb->dbb_permanent;
const SLONG att_id = attachment->att_attachment_id;
// Determine the backup state
int backup_state = backup_state_unknown;
if (!ast)
{
BackupManager* const bm = dbb->dbb_backup_manager;
if (bm && !bm->isShutDown())
{
BackupManager::StateReadGuard holder(tdbb);
switch (bm->getState())
{
case Ods::hdr_nbak_normal:
backup_state = backup_state_normal;
break;
case Ods::hdr_nbak_stalled:
backup_state = backup_state_stalled;
break;
case Ods::hdr_nbak_merge:
backup_state = backup_state_merge;
break;
}
}
}
if (!dbb->dbb_monitoring_data)
dbb->dbb_monitoring_data = FB_NEW(pool) MonitoringData(dbb);
MonitoringData::Guard guard(dbb->dbb_monitoring_data);
dbb->dbb_monitoring_data->cleanup(att_id);
MonitoringData::Writer writer(dbb->dbb_monitoring_data, att_id);
SnapshotData::DumpRecord record(pool);
if (!ast)
putDatabase(record, dbb, writer, fb_utils::genUniqueId(), backup_state);
putAttachment(record, attachment, writer, fb_utils::genUniqueId());
jrd_tra* transaction = NULL;
// Transaction information
for (transaction = attachment->att_transactions; transaction;
transaction = transaction->tra_next)
{
putTransaction(record, transaction, writer, fb_utils::genUniqueId());
}
// Call stack information
for (transaction = attachment->att_transactions; transaction;
transaction = transaction->tra_next)
{
for (jrd_req* request = transaction->tra_requests;
request && (request->req_flags & req_active) && (request->req_transaction == transaction);
request = request->req_caller)
{
request->adjustCallerStats();
if (!(request->getStatement()->flags &
(JrdStatement::FLAG_INTERNAL | JrdStatement::FLAG_SYS_TRIGGER)) &&
request->req_caller)
{
putCall(record, request, writer, fb_utils::genUniqueId());
}
}
}
// Request information
for (const jrd_req* const* i = attachment->att_requests.begin();
i != attachment->att_requests.end();
++i)
{
const jrd_req* const request = *i;
if (!(request->getStatement()->flags &
(JrdStatement::FLAG_INTERNAL | JrdStatement::FLAG_SYS_TRIGGER)))
{
const string plan = OPT_get_plan(tdbb, request, true);
putRequest(record, request, writer, fb_utils::genUniqueId(), plan);
}
}
}
void Monitoring::publishAttachment(thread_db* tdbb)
{
Database* const dbb = tdbb->getDatabase();
Attachment* const attachment = tdbb->getAttachment();
if (!dbb->dbb_monitoring_data)
dbb->dbb_monitoring_data = FB_NEW(*dbb->dbb_permanent) MonitoringData(dbb);
MonitoringData::Guard guard(dbb->dbb_monitoring_data);
dbb->dbb_monitoring_data->setup(attachment->att_attachment_id);
}
void Monitoring::cleanupAttachment(thread_db* tdbb)
{
Database* const dbb = tdbb->getDatabase();
Attachment* const attachment = tdbb->getAttachment();
if (dbb->dbb_monitoring_data)
{
MonitoringData::Guard guard(dbb->dbb_monitoring_data);
dbb->dbb_monitoring_data->cleanup(attachment->att_attachment_id);
}
}