blob: dc8114af0e5c20519261a2ab8f36386c31ff353d [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2020 Eclipse APP4MC contributors.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************
*/
package org.eclipse.app4mc.atdb;
import java.sql.SQLException;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ATDBBuilder {
private final DBConnection connection;
public ATDBBuilder(final DBConnection connection) throws SQLException {
this.connection = connection;
}
public ATDBBuilder createBasicDBStructure() throws SQLException {
// create meta info table
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS metaInformation (\n"
+ " name TEXT PRIMARY KEY,\n"
+ " value TEXT\n"
+ ");");
// create entity type table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityType (\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL UNIQUE\n"
+ ");");
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS entityTypeIndex ON entityType(name);");
// create entity table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entity (\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL UNIQUE,\n"
+ " entityTypeId REFERENCES entityType(id)\n"
+ ");");
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS entityIndex ON entity(name);");
// create entity instance table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityInstance (\n"
+ " entityId REFERENCES entity(id),\n"
+ " sqcnr INTEGER,\n"
+ " PRIMARY KEY(entityId, sqcnr)\n"
+ ");");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityInstanceIndex ON entityInstance(entityId, sqcnr);");
// create event type table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS eventType (\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL UNIQUE\n"
+ ");");
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS eventTypeIndex ON eventType(name);");
// create property table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS property (\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL UNIQUE,\n"
+ " type TEXT"//
+ ");");
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS propertyIndex ON property(name);");
// create property value table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS propertyValue (\n"
+ " entityId REFERENCES entity(id),\n"
+ " propertyId REFERENCES property(id),\n"
+ " sqcnr INTEGER DEFAULT 0,\n"
+ " value TEXT,\n"
+ " PRIMARY KEY(entityId, propertyId, sqcnr)\n"
+ ");");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS propertyValueIndex ON propertyValue(entityId, propertyId);");
// create event table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS event (\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL UNIQUE,\n"
+ " eventTypeId REFERENCES eventType,\n"
+ " entityId REFERENCES entity,\n"
+ " sourceEntityId REFERENCES entity DEFAULT NULL\n"
+ ");");
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS eventIndex ON event(name);");
// create metric table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS metric (\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " name TEXT NOT NULL UNIQUE,\n"
+ " dimension TEXT NOT NULL\n"
+ ");");
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS metricIndex ON metric(name);");
// create entity metric instance value table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityMetricInstanceValue (\n"
+ " entityId REFERENCES entity(id),\n"
+ " metricId REFERENCES metric(id),\n"
+ " sqcnr INTEGER,\n"
+ " value TEXT,\n"
+ " PRIMARY KEY(entityId, metricId, sqcnr)\n"
+ ");");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityMetricInstanceValueIndex ON entityMetricInstanceValue(entityId, metricId, sqcnr);");
// create entity instance metric value table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityInstanceMetricValue (\n"
+ " entityId ,\n"
+ " entityInstance ,\n"
+ " metricId REFERENCES metric(id),\n"
+ " value TEXT,\n"
+ " PRIMARY KEY(entityId, entityInstance, metricId),\n"
+ " FOREIGN KEY(entityId, entityInstance) REFERENCES entityInstance\n"
+ ");");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityInstanceMetricValueIndex ON entityInstanceMetricValue(entityId, entityInstance, metricId);");
// create entity metric value table and index
this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityMetricValue (\n"
+ " entityId REFERENCES entity(id),\n"
+ " metricId REFERENCES metric(id),\n"
+ " value TEXT,\n"
+ " PRIMARY KEY(entityId, metricId)\n"
+ ");");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityMetricValueIndex ON entityMetricValue(entityId, metricId);");
return this;
}
public ATDBBuilder createBasicViews() throws SQLException {
final String idRefCase = " WHEN %2$s.propertyId IN (SELECT id FROM property WHERE type = '%1$sIdRef') THEN\n"
+ " (SELECT name FROM %1$s WHERE id = %2$s.value)\n";
// human readable entity property values
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vPropertyValue AS SELECT\n"
+ " (SELECT name FROM entity WHERE id = propertyValue.entityId) AS entityName,\n"
+ " (SELECT name FROM entityType WHERE id = "
+ "(SELECT entityTypeId FROM entity WHERE id = propertyValue.entityId)"
+ ") AS entityType,\n"
+ " (SELECT name FROM property WHERE id = propertyValue.propertyId) AS propertyName,\n"
+ " (SELECT type FROM property WHERE id = propertyValue.propertyId) AS propertyType,\n"
+ " (GROUP_CONCAT(CASE\n"
+ Stream.of("entity", "event").map(tableName -> String.format(idRefCase, tableName, "propertyValue"))
.collect(Collectors.joining())
+ " ELSE\n"
+ " propertyValue.value\n"
+ " END, ', ')) AS value\n"
+ "FROM propertyValue GROUP BY entityId, propertyId ORDER BY entityId, propertyId;");
// human readable observable events
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEvent AS SELECT\n"
+ " name,\n"
+ " (SELECT name FROM eventType WHERE id = eventTypeId) AS eventType,\n"
+ " (SELECT name FROM entity WHERE id = entityId) AS entityName,\n"
+ " (SELECT name FROM entityType WHERE id =\n"
+ " (SELECT entityTypeId FROM entity WHERE id = event.entityId)\n"
+ " ) AS entityType,\n"
+ " (SELECT name FROM entity WHERE id = sourceEntityId) AS sourceEntityName,\n"
+ " (SELECT name FROM entityType WHERE id =\n"
+ " (SELECT entityTypeId FROM entity WHERE id = event.sourceEntityId)\n"
+ " ) AS sourceEntityType\n"
+ "FROM event;");
// human readable event chain table (extract from entity)
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEventChainEntity AS SELECT\n"
+ " name AS eventChainName,\n"
+ " (SELECT GROUP_CONCAT((SELECT name FROM event WHERE id = value), ', ') FROM propertyValue WHERE\n"
+ " entityId = ecEntity.id AND propertyId = (SELECT id FROM property WHERE name = 'ecStimulus')) AS stimulus,\n"
+ " (SELECT GROUP_CONCAT((SELECT name FROM event WHERE id = value), ', ') FROM propertyValue WHERE\n"
+ " entityId = ecEntity.id AND propertyId = (SELECT id FROM property WHERE name = 'ecResponse')) AS response,\n"
+ " (SELECT GROUP_CONCAT((SELECT name FROM entity WHERE id = value), ', ') FROM propertyValue WHERE\n"
+ " entityId = ecEntity.id AND propertyId = (SELECT id FROM property WHERE name = 'ecItems')) AS items,\n"
+ " (SELECT value FROM propertyValue WHERE entityId = ecEntity.id AND propertyId ="
+ " (SELECT id FROM property WHERE name = 'ecMinItemsCompleted')) AS minItemsCompleted,\n"
+ " EXISTS(SELECT value FROM propertyValue WHERE entityId = ecEntity.id AND propertyId ="
+ " (SELECT id FROM property WHERE name = 'ecMinItemsCompleted')) AS isParallel\n"
+ "FROM entity AS ecEntity WHERE entityTypeId = (SELECT id FROM entityType WHERE entityType.name = 'EC');");
// human readable entity specific metric instances
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityMetricInstanceValue AS SELECT\n"
+ " (SELECT name FROM entity WHERE id = entityMetricInstanceValue.entityId) AS entityName,\n"
+ " (SELECT name FROM entityType WHERE id =\n"
+ " (SELECT entityTypeId FROM entity WHERE id = entityMetricInstanceValue.entityId)\n"
+ " ) AS entityType,\n"
+ " (SELECT name FROM metric WHERE id = entityMetricInstanceValue.metricId) AS metricName,\n"
+ " entityMetricInstanceValue.sqcnr,\n"
+ " entityMetricInstanceValue.value\n"
+ "FROM entityMetricInstanceValue\n"
+ "ORDER BY entityId, metricId, sqcnr;");
// human readable entity instance specific metrics
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityInstanceMetricValue AS SELECT\n"
+ " (SELECT name FROM entity WHERE id = entityInstanceMetricValue.entityId) AS entityName,\n"
+ " (SELECT name FROM entityType WHERE id =\n"
+ " (SELECT entityTypeId FROM entity WHERE id = entityInstanceMetricValue.entityId)\n"
+ " ) AS entityType,\n"
+ " entityInstanceMetricValue.entityInstance,\n"
+ " (SELECT name FROM metric WHERE id = entityInstanceMetricValue.metricId) AS metricName,\n"
+ " entityInstanceMetricValue.value\n"
+ "FROM entityInstanceMetricValue\n"
+ "ORDER BY entityId, entityInstance, metricId;");
// human readable entity specific metrics
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityMetricValue AS SELECT\n"
+ " (SELECT name FROM entity WHERE id = entityMetricValue.entityId) AS entityName,\n"
+ " (SELECT name FROM entityType WHERE id =\n"
+ " (SELECT entityTypeId FROM entity WHERE id = entityMetricValue.entityId)\n"
+ " ) AS entityType,\n"
+ " (SELECT name FROM metric WHERE id = entityMetricValue.metricId) AS metricName,\n"
+ " entityMetricValue.value\n"
+ "FROM entityMetricValue\n"
+ "ORDER BY entityId, metricId;");
return this;
}
private static String getPersistableTablePrefix(final boolean persistTable) {
return "CREATE" + (persistTable ? " " : " TEMPORARY ") + "TABLE IF NOT EXISTS ";
}
private static String getInstRuntimeEventsQuery(final EntityType entityType) {
final String events = entityType.getPossibleEvents().stream().map(e -> "'" + e.toString().toLowerCase() + "'")
.collect(Collectors.joining(", "));
final String traceAliases = entityType.getTraceAliases().stream().map(ta -> "'" + ta + "'").collect(Collectors.joining(", "));
return " SELECT timestamp, sqcnr, entityId, entityInstance, sourceEntityId, sourceEntityInstance, eventTypeId\n"//
+ " FROM traceEvent WHERE\n"//
+ " eventTypeId IN (SELECT id FROM eventType WHERE name IN (" + events + ")) AND\n"//
+ " entityId IN (SELECT id FROM entity WHERE entityTypeId IN\n"//
+ " (SELECT id FROM entityType WHERE name IN (" + traceAliases + "))\n"//
+ " )\n"//
+ " GROUP BY entityId, entityInstance, timestamp, sqcnr";
}
private static String getInstEventInfosQuery(final EntityType entityType) {
return "SELECT *, (" + String.join(" AND ", entityType.getValidityConstraints()) + ") isComplete "//
+ "FROM (SELECT"//
+ " entityId,"//
+ " entityInstance,"//
+ entityType.getPossibleEvents().stream().map(e -> //
" SUM(CASE WHEN eventTypeId = (SELECT id FROM eventType WHERE name = '" + e.toString().toLowerCase() + //
"') THEN 1 ELSE 0 END) " + e + "EventCount").collect(Collectors.joining(","))//
+ " FROM " + entityType.getName() + "InstanceRuntimeTraceEvent GROUP BY entityId, entityInstance)";
}
public ATDBBuilder createOptionalAndTemporaryTables(final Collection<? extends EntityType> entityTypes, final boolean persistOptionalTables) throws SQLException {
final String persistableTablePref = getPersistableTablePrefix(persistOptionalTables);
// create traceEvent table and indices
this.connection.executeUpdate(persistableTablePref + "traceEvent (\n"
+ " timestamp INTEGER,\n"
+ " sqcnr INTEGER,\n"
+ " entityId INTEGER,\n"
+ " entityInstance INTEGER,\n"
+ " sourceEntityId INTEGER,\n"
+ " sourceEntityInstance INTEGER,\n"
+ " eventTypeId INTEGER,\n"
+ " value TEXT,\n"
+ " PRIMARY KEY(timestamp, sqcnr)"
+ (persistOptionalTables ? ",\n FOREIGN KEY(entityId, entityInstance) REFERENCES entityInstance"
+ ",\n FOREIGN KEY(sourceEntityId, sourceEntityInstance) REFERENCES entityInstance"
+ ",\n FOREIGN KEY(eventTypeId) REFERENCES eventType\n" : "\n")
+ ");");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS traceEventIndex ON traceEvent(timestamp, sqcnr);");
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS traceEventIndexForECs ON traceEvent(entityId, eventTypeId);");
createTemporaryEntityFilteredTraceEventTables(entityTypes);
for (final EntityType et : entityTypes) {
// extract runtime relevant traceEvent counts per entity instance
this.connection.executeUpdate(persistableTablePref + et.getName() + "InstanceTraceInfo (\n"
+ " entityId INTEGER,\n"
+ " entityInstance INTEGER,\n"
+ et.getPossibleEvents().stream().map(e -> e.toString().toLowerCase() + "EventCount INTEGER,\n").collect(Collectors.joining())
+ " isComplete BOOLEAN,\n"
+ " PRIMARY KEY(entityId, entityInstance)\n"
+ ");");
// create index for better performance on trace events
this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS " + et.getName() + "InstanceTraceInfoIndex ON " + et.getName()
+ "InstanceTraceInfo(entityId, entityInstance);");
}
// create event chain info table
this.connection.executeUpdate(persistableTablePref + "eventChainInstanceInfo (\n"//
+ " entityId INTEGER,\n"//
+ " entityInstance INTEGER,\n"//
+ " stimulusTimestamp INTEGER,\n"//
+ " stimulusSqcnr INTEGER,\n"//
+ " responseTimestamp INTEGER,\n"//
+ " responseSqcnr INTEGER,\n"//
+ " isAge BOOLEAN,\n"//
+ " isReaction BOOLEAN,\n"//
+ " PRIMARY KEY(entityId, entityInstance),\n"//
+ " FOREIGN KEY(stimulusTimestamp, stimulusSqcnr) REFERENCES traceEvent,\n"//
+ " FOREIGN KEY(responseTimestamp, responseSqcnr) REFERENCES traceEvent"//
+ (persistOptionalTables ? ",\n FOREIGN KEY(entityId, entityInstance) REFERENCES entityInstance" : "")//
+ "\n);");
return this;
}
public ATDBBuilder createTemporaryEntityFilteredTraceEventTables(final Collection<? extends EntityType> entityTypes) throws SQLException {
for (final EntityType et : entityTypes) {
// collect/filter runtime relevant events per entity type
this.connection.executeUpdate("CREATE TEMPORARY TABLE IF NOT EXISTS " + et.getName() + "InstanceRuntimeTraceEvent (\n"
+ " timestamp INTEGER,\n"
+ " sqcnr INTEGER,\n"
+ " entityId INTEGER,\n"
+ " entityInstance INTEGER,\n"
+ " sourceEntityId INTEGER,\n"
+ " sourceEntityInstance INTEGER,\n"
+ " eventTypeId INTEGER,\n"
+ " PRIMARY KEY(timestamp, sqcnr)\n"
+ ");");
// create index for better performance on trace events
this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS " + et.getName() + "InstanceRuntimeTraceEventIndex ON "
+ et.getName() + "InstanceRuntimeTraceEvent(entityId, entityInstance, eventTypeId);");
}
return this;
}
/**
* This only works if the optional tables have been persisted.
* @return
* @throws SQLException
*/
public ATDBBuilder createOptionalViews(final Collection<? extends EntityType> entityTypes) throws SQLException {
// human readable event table
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vTraceEvent AS SELECT\n"//
+ " traceEvent.timestamp,\n"//
+ " traceEvent.sqcnr,\n"//
+ " (SELECT name FROM entity WHERE id = traceEvent.entityId) AS entityName,\n"//
+ " (SELECT name FROM entityType WHERE id =\n"//
+ " (SELECT entityTypeId FROM entity WHERE id = traceEvent.entityId)\n"//
+ " ) AS entityType,\n"//
+ " traceEvent.entityInstance,\n"//
+ " (SELECT name FROM entity WHERE id = traceEvent.sourceEntityId) AS sourceEntityName,\n"//
+ " (SELECT name FROM entityType WHERE id =\n"//
+ " (SELECT entityTypeId FROM entity WHERE id = traceEvent.sourceEntityId)\n"//
+ " ) AS sourceEntityType,\n"//
+ " traceEvent.sourceEntityInstance,\n"//
+ " (SELECT name FROM eventType WHERE id = traceEvent.eventTypeId) AS eventType,\n"//
+ " traceEvent.value\n"//
+ "FROM traceEvent;");
for (final EntityType et : entityTypes) {
final String reDBName = et.getName() + "InstanceRuntimeTraceEvent";
// human readable intermediate view for all runtime relevant events per entity instance
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS v" + reDBName + " AS SELECT\n"//
+ " timestamp,\n"//
+ " sqcnr,\n"//
+ " (SELECT name FROM entity WHERE id = entityId) AS " + et.getUCName() + "Name,\n"//
+ " entityInstance,\n"//
+ " (SELECT name FROM entity WHERE id = sourceEntityId) AS sourceEntityName,\n"//
+ " sourceEntityInstance,\n"//
+ " (SELECT name FROM eventType WHERE id = " + "eventTypeId) AS eventType\n"//
+ "FROM (\n" + getInstRuntimeEventsQuery(et) + "\n);");
// human readable intermediate view for all runtime relevant traceEvent counts per entity instance
final String eiDBName = et.getName() + "InstanceTraceInfo";
final String eventCountColumns = et.getPossibleEvents().stream()
.map(ev -> eiDBName + "." + ev.toString().toLowerCase() + "EventCount").collect(Collectors.joining(",\n "));
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS v" + eiDBName + " AS SELECT\n"//
+ " (SELECT name FROM entity WHERE id = " + eiDBName + ".entityId) AS " + et.getUCName() + "Name,\n"//
+ " " + eiDBName + ".entityInstance,\n"//
+ " " + eventCountColumns + ",\n"//
+ " " + eiDBName + ".isComplete\n"//
+ "FROM " + eiDBName + ";");
}
// human readable event chain instance info table
this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEventChainInstanceInfo AS SELECT\n"//
+ " (SELECT name FROM entity WHERE id = entityId) AS eventChainName,\n"//
+ " entityInstance AS ecInstance,\n"//
+ " stimulusTimestamp,\n"//
+ " (SELECT name FROM entity WHERE id = (SELECT entityId FROM traceEvent WHERE timestamp = stimulusTimestamp "//
+ "AND sqcnr = stimulusSqcnr)) AS stimulusEntityName,\n"//
+ " (SELECT entityInstance FROM traceEvent WHERE timestamp = stimulusTimestamp "//
+ "AND sqcnr = stimulusSqcnr) AS stimulusEntityInstance,\n"//
+ " (SELECT name FROM eventType WHERE id = (SELECT eventTypeId FROM traceEvent WHERE timestamp = stimulusTimestamp "//
+ "AND sqcnr = stimulusSqcnr)) AS stimulusEvent,\n"//
+ " responseTimestamp,\n"//
+ " (SELECT name FROM entity WHERE id = (SELECT entityId FROM traceEvent WHERE timestamp = responseTimestamp "//
+ "AND sqcnr = responseSqcnr)) AS responseEntityName,\n"//
+ " (SELECT entityInstance FROM traceEvent WHERE timestamp = responseTimestamp "//
+ "AND sqcnr = responseSqcnr) AS responseEntityInstance,\n"//
+ " (SELECT name FROM eventType WHERE id = (SELECT eventTypeId FROM traceEvent WHERE timestamp = responseTimestamp "//
+ "AND sqcnr = responseSqcnr)) AS responseEvent,\n"//
+ " (CASE WHEN isAge AND isReaction THEN 'age/reaction' WHEN isAge THEN 'age' "//
+ "WHEN isReaction THEN 'reaction' END) AS latencyType\n"//
+ "FROM eventChainInstanceInfo;");
return this;
}
public void autoPopulateEntityFilteredTraceEventTables(final Collection<? extends EntityType> entityTypes) throws SQLException {
for (final EntityType et : entityTypes) {
// collect/filter runtime relevant events per entity instance
this.connection.executeUpdate("INSERT INTO " + et.getName() + "InstanceRuntimeTraceEvent\n" + getInstRuntimeEventsQuery(et) + ";");
// extract runtime relevant traceEvent counts per entity instance
this.connection.executeUpdate("INSERT INTO " + et.getName() + "InstanceTraceInfo\n" + getInstEventInfosQuery(et) + ";");
}
}
}