/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.service.is;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.ais.model.Group;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.ais.model.aisb2.AISBBasedBuilder;
import com.foundationdb.ais.model.aisb2.NewAISBuilder;
import com.foundationdb.qp.virtualadapter.BasicFactoryBase;
import com.foundationdb.qp.virtualadapter.VirtualAdapter;
import com.foundationdb.qp.virtualadapter.VirtualGroupCursor.GroupScan;
import com.foundationdb.qp.virtualadapter.SimpleVirtualGroupScan;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.row.ValuesHolderRow;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.sql.LayerInfoInterface;
import com.foundationdb.server.error.ErrorCode;
import com.foundationdb.server.error.ErrorCodeClass;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.config.ConfigurationService;
import com.foundationdb.server.service.monitor.CursorMonitor;
import com.foundationdb.server.service.monitor.MonitorService;
import com.foundationdb.server.service.monitor.MonitorStage;
import com.foundationdb.server.service.monitor.PreparedStatementMonitor;
import com.foundationdb.server.service.monitor.ServerMonitor;
import com.foundationdb.server.service.monitor.SessionMonitor;
import com.foundationdb.server.service.monitor.UserMonitor;
import com.foundationdb.server.service.monitor.SessionMonitor.StatementTypes;
import com.foundationdb.server.service.security.SecurityService;
import com.foundationdb.server.service.session.Session;
import com.foundationdb.server.store.SchemaManager;
import com.foundationdb.server.store.Store;
import com.foundationdb.server.types.common.types.TypesTranslator;
import com.foundationdb.util.tap.Tap;
import com.foundationdb.util.tap.TapReport;
import com.google.common.collect.Iterators;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServerSchemaTablesServiceImpl
extends SchemaTablesService
implements Service, ServerSchemaTablesService {
private static final Logger LOG = LoggerFactory.getLogger(ServerSchemaTablesServiceImpl.class);
static final TableName ERROR_CODES = new TableName (SCHEMA_NAME, "error_codes");
static final TableName ERROR_CODE_CLASSES = new TableName (SCHEMA_NAME, "error_code_classes");
static final TableName SERVER_INSTANCE_SUMMARY = new TableName (SCHEMA_NAME, "server_instance_summary");
static final TableName SERVER_SERVERS = new TableName (SCHEMA_NAME, "server_servers");
static final TableName SERVER_SESSIONS = new TableName (SCHEMA_NAME, "server_sessions");
static final TableName SERVER_STATISTICS = new TableName (SCHEMA_NAME, "server_statistics_summary");
static final TableName SERVER_PARAMETERS = new TableName (SCHEMA_NAME, "server_parameters");
static final TableName SERVER_MEMORY_POOLS = new TableName (SCHEMA_NAME, "server_memory_pools");
static final TableName SERVER_GARBAGE_COLLECTORS = new TableName (SCHEMA_NAME, "server_garbage_collectors");
static final TableName SERVER_TAPS = new TableName (SCHEMA_NAME, "server_taps");
static final TableName SERVER_PREPARED_STATEMENTS = new TableName (SCHEMA_NAME, "server_prepared_statements");
static final TableName SERVER_CURSORS = new TableName (SCHEMA_NAME, "server_cursors");
static final TableName SERVER_USERS = new TableName (SCHEMA_NAME, "server_users");
private final MonitorService monitor;
private final ConfigurationService configService;
private final LayerInfoInterface serverInterface;
private final SecurityService securityService;
private final Store store;
private volatile String hostname;
@Inject
public ServerSchemaTablesServiceImpl (SchemaManager schemaManager,
MonitorService monitor,
ConfigurationService configService,
LayerInfoInterface serverInterface,
SecurityService securityService,
Store store) {
super(schemaManager);
this.monitor = monitor;
this.configService = configService;
this.serverInterface = serverInterface;
this.securityService = securityService;
this.store = store;
}
private String getHostname() {
// Compute and cache as host lookup may be expensive.
if(hostname == null) {
synchronized(this) {
if(hostname == null) {
// TODO: Consider recomputing this periodically? See also FDBMetricsService.
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch(UnknownHostException ex) {
LOG.error("Unable to retrieve hostname", ex);
hostname = "localhost";
}
}
}
}
return hostname;
}
@Override
public void start() {
AkibanInformationSchema ais = createTablesToRegister(schemaManager.getTypesTranslator());
// ERROR_CODES
attach (ais, ERROR_CODES, ServerErrorCodes.class);
// ERROR_CODE_CLASSES
attach (ais, ERROR_CODE_CLASSES, ServerErrorCodeClasses.class);
//SERVER_INSTANCE_SUMMARY
attach (ais, SERVER_INSTANCE_SUMMARY, InstanceSummary.class);
//SERVER_SERVERS
attach (ais, SERVER_SERVERS, Servers.class);
//SERVER_SESSIONS
attach (ais, SERVER_SESSIONS, Sessions.class);
//SERVER_STATISTICS
attach (ais, SERVER_STATISTICS, Statistics.class);
//SERVER_PARAMETERS
attach (ais, SERVER_PARAMETERS, ServerParameters.class);
//SERVER_MEMORY_POOLS
attach (ais, SERVER_MEMORY_POOLS, ServerMemoryPools.class);
//SERVER_GARBAGE_COLLECTIONS
attach (ais, SERVER_GARBAGE_COLLECTORS, ServerGarbageCollectors.class);
//SERVER_TAPS
attach (ais, SERVER_TAPS, ServerTaps.class);
//SERVER_PREPARED_STATEMENTS
attach (ais, SERVER_PREPARED_STATEMENTS, PreparedStatements.class);
//SERVER_CURSORS
attach (ais, SERVER_CURSORS, Cursors.class);
//SERVER_USERS
attach(ais, SERVER_USERS, Users.class);
}
@Override
public void stop() {
// nothing
}
@Override
public void crash() {
// nothing
}
protected Collection<SessionMonitor> getAccessibleSessions(Session session) {
if (securityService.hasRestrictedAccess(session)) {
return monitor.getSessionMonitors();
}
else {
SessionMonitor sm = monitor.getSessionMonitor(session);
if (sm == null) {
return Collections.emptyList();
}
else {
return Collections.singletonList(sm);
}
}
}
protected Collection<UserMonitor> getAccessibleUsers (Session session) {
if (securityService.hasRestrictedAccess(session)) {
return monitor.getUserMonitors();
} else {
UserMonitor um = monitor.getUserMonitor(session);
if (um == null) {
return Collections.emptyList();
} else {
return Collections.singletonList(um);
}
}
}
private class InstanceSummary extends BasicFactoryBase {
public InstanceSummary(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
return new Scan(adapter.getSession(), getRowType(group.getAIS()));
}
@Override
public long rowCount(Session session) {
return 1L;
}
private class Scan extends BaseScan {
public Scan (Session session, RowType rowType) {
super(rowType);
}
@Override
public Row next() {
if (rowCounter != 0) {
return null;
}
Long compile_time = ManagementFactory.getCompilationMXBean().getTotalCompilationTime();
return new ValuesHolderRow(rowType,
serverInterface.getServerName(),
serverInterface.getVersionInfo().versionLong,
getHostname(),
store.getName(),
compile_time,
configService.getInstanceID(),
++rowCounter);
}
}
}
private class Servers extends BasicFactoryBase {
public Servers(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<ServerMonitor> servers = monitor.getServerMonitors().values().iterator();
return new SimpleVirtualGroupScan<ServerMonitor>(group.getAIS(), getName(), servers) {
@Override
protected Object[] createRow(ServerMonitor data, int hiddenPk) {
return new Object[] {
data.getServerType(),
(data.getLocalPort() < 0) ? null : Long.valueOf(data.getLocalPort()),
data.getStartTimeMillis() / 1000,
Long.valueOf(data.getSessionCount()),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return monitor.getServerMonitors().size();
}
}
private class Statistics extends BasicFactoryBase {
public Statistics(TableName sourceTable) {
super (sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
return new Scan(adapter.getSession(), getRowType(group.getAIS()));
}
@Override
public long rowCount(Session session) {
return 1L;
}
private class Scan extends BaseScan {
public Scan (Session session, RowType rowType) {
super(rowType);
}
@Override
public Row next() {
if (rowCounter != 0) {
return null;
}
return new ValuesHolderRow(rowType,
monitor.getCount(StatementTypes.STATEMENT),
monitor.getCount(StatementTypes.FAILED),
monitor.getCount(StatementTypes.FROM_CACHE),
monitor.getCount(StatementTypes.LOGGED),
monitor.getCount(StatementTypes.CALL_STMT),
monitor.getCount(StatementTypes.DDL_STMT),
monitor.getCount(StatementTypes.DML_STMT),
monitor.getCount(StatementTypes.SELECT),
monitor.getCount(StatementTypes.OTHER_STMT),
++rowCounter);
}
}
}
private class Sessions extends BasicFactoryBase {
public Sessions(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<SessionMonitor> sessions = getAccessibleSessions(adapter.getSession()).iterator();
return new SimpleVirtualGroupScan<SessionMonitor>(group.getAIS(), getName(), sessions) {
@Override
protected Object[] createRow(SessionMonitor data, int hiddenPk) {
MonitorStage stage = data.getCurrentStage();
return new Object[] {
(long)data.getSessionId(),
data.getCallerSessionId() < 0 ? null : (long)data.getCallerSessionId(),
data.getStartTimeMillis() / 1000,
data.getServerType(),
data.getRemoteAddress(),
stage == null ? null : stage.name(),
data.getStatementCount(),
data.getCurrentStatement(),
data.getCurrentStatementStartTimeMillis() > 0 ? data.getCurrentStatementStartTimeMillis() / 1000 : null,
data.getCurrentStatementEndTimeMillis() > 0 ? data.getCurrentStatementEndTimeMillis() / 1000 : null,
data.getRowsProcessed() < 0 ? null : (long)data.getRowsProcessed(),
data.getCurrentStatementPreparedName(),
data.getCount(StatementTypes.FAILED),
data.getCount(StatementTypes.FROM_CACHE),
data.getCount(StatementTypes.LOGGED),
data.getCount(StatementTypes.CALL_STMT),
data.getCount(StatementTypes.DDL_STMT),
data.getCount(StatementTypes.DML_STMT),
data.getCount(StatementTypes.SELECT),
data.getCount(StatementTypes.OTHER_STMT),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return monitor.getSessionMonitors().size();
}
}
private class ServerErrorCodes extends BasicFactoryBase {
public ServerErrorCodes(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<ErrorCode> errorCodes = Iterators.forArray(ErrorCode.values());
return new SimpleVirtualGroupScan<ErrorCode>(group.getAIS(), getName(), errorCodes) {
@Override
protected Object[] createRow(ErrorCode data, int hiddenPk) {
return new Object[] {
data.getFormattedValue(),
data.name(),
data.getMessage(),
null,
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return ErrorCode.values().length;
}
}
private class ServerErrorCodeClasses extends BasicFactoryBase {
public ServerErrorCodeClasses(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<ErrorCodeClass> errorCodes = ErrorCodeClass.getClasses().iterator();
return new SimpleVirtualGroupScan<ErrorCodeClass>(group.getAIS(), getName(), errorCodes) {
@Override
protected Object[] createRow(ErrorCodeClass data, int hiddenPk) {
return new Object[] {
data.getKey(),
data.getDescription(),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return ErrorCodeClass.getClasses().size();
}
}
private class ServerParameters extends BasicFactoryBase {
public ServerParameters(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<Map.Entry<String,String>> properties = configService.getProperties().entrySet().iterator();
return new SimpleVirtualGroupScan<Entry<String,String>>(group.getAIS(), getName(), properties) {
@Override
protected Object[] createRow(Map.Entry<String,String> data, int hiddenPk) {
return new Object[] {
data.getKey(),
data.getValue(),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return configService.getProperties().size();
}
}
private class ServerMemoryPools extends BasicFactoryBase {
public ServerMemoryPools(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans().iterator();
return new SimpleVirtualGroupScan<MemoryPoolMXBean>(group.getAIS(), getName(), memoryPools) {
@Override
protected Object[] createRow(MemoryPoolMXBean data, int hiddenPk) {
return new Object[] {
data.getName(),
data.getType().name(),
data.getUsage().getUsed(),
data.getUsage().getMax(),
data.getPeakUsage().getUsed(),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return ManagementFactory.getMemoryPoolMXBeans().size();
}
}
private class ServerGarbageCollectors extends BasicFactoryBase {
public ServerGarbageCollectors(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<GarbageCollectorMXBean> collectors = ManagementFactory.getGarbageCollectorMXBeans().iterator();
return new SimpleVirtualGroupScan<GarbageCollectorMXBean>(group.getAIS(), getName(), collectors) {
@Override
protected Object[] createRow(GarbageCollectorMXBean data, int hiddenPk) {
return new Object[] {
data.getName(),
data.getCollectionCount(),
data.getCollectionTime(),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return ManagementFactory.getGarbageCollectorMXBeans().size();
}
}
private class ServerTaps extends BasicFactoryBase {
private TapReport[] getAllReports() {
return Tap.getReport(".*");
}
public ServerTaps(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<TapReport> taps = Iterators.forArray(Tap.getReport(".*"));
return new SimpleVirtualGroupScan<TapReport>(group.getAIS(), getName(), taps) {
@Override
protected Object[] createRow(TapReport data, int hiddenPk) {
return new Object[] {
data.getName(),
data.getInCount(),
data.getOutCount(),
data.getCumulativeTime(),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return getAllReports().length;
}
}
private class PreparedStatements extends BasicFactoryBase {
public PreparedStatements(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
return new Scan(adapter.getSession(), getRowType(group.getAIS()));
}
@Override
public long rowCount(Session session) {
long total = 0;
for (SessionMonitor smon : monitor.getSessionMonitors())
total += smon.getPreparedStatements().size();
return total;
}
private class Scan extends BaseScan {
final Iterator<SessionMonitor> sessions;
Iterator<PreparedStatementMonitor> statements = null;
public Scan(Session session, RowType rowType) {
super(rowType);
sessions = getAccessibleSessions(session).iterator();
}
@Override
public Row next() {
while ((statements == null) ||
!statements.hasNext()) {
if (!sessions.hasNext()) {
return null;
}
statements = sessions.next().getPreparedStatements().iterator();
}
PreparedStatementMonitor preparedStatement = statements.next();
return new ValuesHolderRow(rowType,
(long)preparedStatement.getSessionId(),
preparedStatement.getName(),
preparedStatement.getSQL(),
preparedStatement.getPrepareTimeMillis()/1000,
preparedStatement.getEstimatedRowCount() < 0 ? null : (long)preparedStatement.getEstimatedRowCount(),
++rowCounter);
}
}
}
private class Cursors extends BasicFactoryBase {
public Cursors(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
return new Scan(adapter.getSession(), getRowType( group.getAIS()));
}
@Override
public long rowCount(Session session) {
long total = 0;
for (SessionMonitor smon : monitor.getSessionMonitors())
total += smon.getCursors().size();
return total;
}
private class Scan extends BaseScan {
final Iterator<SessionMonitor> sessions;
Iterator<CursorMonitor> statements = null;
public Scan(Session session, RowType rowType) {
super(rowType);
sessions = getAccessibleSessions(session).iterator();
}
@Override
public Row next() {
while ((statements == null) ||
!statements.hasNext()) {
if (!sessions.hasNext()) {
return null;
}
statements = sessions.next().getCursors().iterator();
}
CursorMonitor cursor = statements.next();
return new ValuesHolderRow(rowType,
(long)cursor.getSessionId(),
cursor.getName(),
cursor.getSQL(),
cursor.getPreparedStatementName(),
cursor.getCreationTimeMillis()/1000,
(long)cursor.getRowCount(),
++rowCounter);
}
}
}
private class Users extends BasicFactoryBase {
public Users(TableName sourceTable) {
super(sourceTable);
}
@Override
public GroupScan getGroupScan(VirtualAdapter adapter, Group group) {
Iterator<UserMonitor> users = getAccessibleUsers(adapter.getSession()).iterator();
return new SimpleVirtualGroupScan<UserMonitor>(group.getAIS(), getName(), users) {
@Override
protected Object[] createRow(UserMonitor data, int hiddenPk) {
return new Object[] {
data.getUserName(),
data.getStatementCount(),
hiddenPk
};
}
};
}
@Override
public long rowCount(Session session) {
return monitor.getUserMonitors().size();
}
}
static AkibanInformationSchema createTablesToRegister(TypesTranslator typesTranslator) {
NewAISBuilder builder = AISBBasedBuilder.create(typesTranslator);
builder.table(SERVER_INSTANCE_SUMMARY)
.colString("server_name", DESCRIPTOR_MAX, false)
.colString("server_version", DESCRIPTOR_MAX, false)
.colString("server_host", IDENT_MAX, false)
.colString("server_store", IDENT_MAX, false)
.colBigInt("server_jit_compiler_time", false)
.colString("server_id", IDENT_MAX, false);
builder.table(SERVER_SERVERS)
.colString("server_type", IDENT_MAX, false)
.colBigInt("local_port", true)
.colSystemTimestamp("start_time", false)
.colBigInt("session_count", true);
builder.table(SERVER_STATISTICS)
.colBigInt("query_count", false)
.colBigInt("failed_query_count", false)
.colBigInt("query_from_cache", false)
.colBigInt("logged_statements", false)
.colBigInt("call_statement_count", false)
.colBigInt("ddl_statement_count", false)
.colBigInt("dml_statement_count", false)
.colBigInt("select_statement_count", false)
.colBigInt("other_statement_count", false)
;
builder.table(SERVER_SESSIONS)
.colBigInt("session_id", false)
.colBigInt("caller_session_id", true)
.colSystemTimestamp("start_time", false)
.colString("server_type", IDENT_MAX, false)
.colString("remote_address", DESCRIPTOR_MAX, true)
.colString("session_status", DESCRIPTOR_MAX, true)
.colBigInt("query_count", false)
.colString("last_query_executed", PATH_MAX, true)
.colSystemTimestamp("query_start_time", true)
.colSystemTimestamp("query_end_time", true)
.colBigInt("query_row_count", true)
.colString("prepared_name", IDENT_MAX, true)
.colBigInt("failed_query_count", false)
.colBigInt("query_from_cache", false)
.colBigInt("logged_statements", false)
.colBigInt("call_statement_count", false)
.colBigInt("ddl_statement_count", false)
.colBigInt("dml_statement_count", false)
.colBigInt("select_statement_count", false)
.colBigInt("other_statement_count", false)
;
builder.table(ERROR_CODES)
.colString("code", 5, false)
.colString("name", DESCRIPTOR_MAX, false)
.colString("message", IDENT_MAX, false)
.colString("description", PATH_MAX, true);
builder.table(ERROR_CODE_CLASSES)
.colString("class", 2, false)
.colString("description", PATH_MAX, true);
builder.table(SERVER_PARAMETERS)
.colString("parameter_name", IDENT_MAX, false)
.colString("current_value", PATH_MAX, false);
builder.table(SERVER_MEMORY_POOLS)
.colString("name", IDENT_MAX, false)
.colString("type", DESCRIPTOR_MAX, false)
.colBigInt("used_bytes", false)
.colBigInt("max_bytes", false)
.colBigInt("peak_bytes", false);
builder.table(SERVER_GARBAGE_COLLECTORS)
.colString("name", IDENT_MAX, false)
.colBigInt("total_count", false)
.colBigInt("total_milliseconds", false);
builder.table(SERVER_TAPS)
.colString("tap_name", IDENT_MAX, false)
.colBigInt("in_count", false)
.colBigInt("out_count", false)
.colBigInt("total_nanoseconds", false);
builder.table(SERVER_PREPARED_STATEMENTS)
.colBigInt("session_id", false)
.colString("prepared_name", IDENT_MAX, true)
.colString("statement", PATH_MAX, true)
.colSystemTimestamp("prepare_time", true)
.colBigInt("estimated_row_count", true);
builder.table(SERVER_CURSORS)
.colBigInt("session_id", false)
.colString("cursor_name", IDENT_MAX, true)
.colString("statement", PATH_MAX, true)
.colString("prepared_name", IDENT_MAX, true)
.colSystemTimestamp("creation_time", true)
.colBigInt("row_count", true);
builder.table(SERVER_USERS)
.colString("user_name", IDENT_MAX, false)
.colBigInt("statement_count", false);
return builder.ais(false);
}
}