blob: 3d9001903496710281754eca0f2e2a770f567bd1 [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2020-2021 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.btf;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
import org.eclipse.app4mc.atdb.ATDBConnection;
import org.eclipse.app4mc.atdb._import.btf.model.BTFEntityType;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.operation.IRunnableWithProgress;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class BTFImporter implements IRunnableWithProgress {
private static final String STIMULI = "stimuli";
private static final String RUNNABLES = "runnables";
private static final String START = "start";
private static final String TERMINATE = "terminate";
private static final String READ_SIGNALS = "readSignals";
private static final String WRITTEN_SIGNALS = "writtenSignals";
private static final String ENTITY_ID_REF = "entityIdRef";
private final String btfFile;
private final ATDBConnection con;
private final long maxBatchSize;
public BTFImporter(final ATDBConnection con, final String btfFile) {
this.con = con;
this.btfFile = btfFile;
final long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory < Long.MAX_VALUE) {
maxBatchSize = maxMemory >> 16;
} else {
maxBatchSize = 2 << 16;
}
}
@Override
public void run(final IProgressMonitor progressMonitor) throws InvocationTargetException {
final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Importing BTF file...", 1);
final SubMonitor readingBTFMonitor = subMon.split(1, SubMonitor.SUPPRESS_NONE);
readingBTFMonitor.beginTask("Reading BTF file...", 10_000);
int readingBTFProgress = 0;
try (final FileInputStream fis = new FileInputStream(btfFile);
final Scanner sc = new Scanner(fis, "UTF-8");
final Statement propStmt = con.createStatement()) {
final PreparedStatement updEntStmt = con.getPreparedStatementFor("UPDATE entity SET entityTypeId = ? WHERE name = ?;");
final PreparedStatement instStmt = con.getPreparedStatementFor("INSERT INTO entityInstance VALUES(?, ?);");
final PreparedStatement evStmt = con.getPreparedStatementFor("INSERT INTO traceEvent VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
final long fileSize = new File(btfFile).length();
long currentTimestamp = Long.MIN_VALUE;
int currentSQCNR = 0;
String currentECU = "";
String currentProcessor = "";
long currentLineNumber = 0;
long currentBatchSize = 0;
long lineLengthSum = 0;
long avgLineLength = 64;
final BiMap<String, String> actCore2Process = HashBiMap.create();
final BiMap<String, String> actProcess2Runnable = HashBiMap.create();
while (sc.hasNextLine()) {
currentLineNumber++;
currentBatchSize++;
final String line = sc.nextLine();
if (!line.startsWith("#")) {
final String[] fields = line.split(",");
if (fields.length > 6) {
final long newTimestamp = Long.parseLong(fields[0]);
final String entityType = fields[3];
final String entityName = fields[4];
final int entityInstance = Integer.parseInt(fields[5]);
final String sourceName = fields[1];
final int sourceInstance = Integer.parseInt(fields[2]);
final String eventType = fields[6];
final Optional<String> value = fields.length > 7 ? Optional.of(fields[7]) : Optional.empty();
insertEntity(entityName, entityType);
if (eventType.equalsIgnoreCase("tag") && value.isPresent()) {
String sourceType = "";
switch (value.get()) {
case "ECU_INIT":
sourceType = "ECU";
currentECU = sourceName;
break;
case "PROCESSOR_INIT":
sourceType = "Processor";
currentProcessor = sourceName;
appendToProperty(currentECU, "processors", ENTITY_ID_REF, sourceName);
break;
case "CORE_INIT":
sourceType = "C";
appendToProperty(currentProcessor, "cores", ENTITY_ID_REF, sourceName);
break;
case "SIG_INIT_VALUE":
sourceType = "SIG";
if (fields.length > 8) {
setProperty(sourceName, "initialValue", "object", fields[8]);
}
break;
default:
setProperty(sourceName, "tag", "object", value.get());
}
if (!sourceType.isEmpty()) {
insertEntity(sourceName, sourceType);
}
} else if (eventType.equalsIgnoreCase("set_frequence") && value.isPresent()) {
setProperty(entityName, "frequencyInHz", "long", value.get());
continue;
} else if ((eventType.equalsIgnoreCase(START) || eventType.equalsIgnoreCase("stop"))
&& entityType.equals("SYS")) {
setProperty(entityName, "system" + eventType.substring(0, 1).toUpperCase() + eventType.substring(1) + "Time",
"time", "" + newTimestamp);
continue;
}
if (BTFEntityType.PROCESS.isTraceAlias(entityType)//
&& "C".equals(entity2Type.getOrDefault(sourceName, ""))//
&& (!entity2property2Value.containsKey(entityName)//
|| !entity2property2Value.get(entityName).containsKey("executingCore"))) {
setProperty(entityName, "executingCore", ENTITY_ID_REF, sourceName);
}
if (BTFEntityType.RUNNABLE.isTraceAlias(entityType)//
&& BTFEntityType.PROCESS.isTraceAlias(entity2Type.getOrDefault(sourceName, ""))//
&& (!entity2property2Value.containsKey(sourceName) //
|| !entity2property2Value.get(sourceName).containsKey(RUNNABLES) //
|| !entity2property2Value.get(sourceName).get(RUNNABLES).contains(entityName))) {
appendToProperty(sourceName, RUNNABLES, ENTITY_ID_REF, entityName);
}
if (eventType.equalsIgnoreCase("activate") && BTFEntityType.PROCESS.isTraceAlias(entityType)) {
insertEntity(sourceName, "STI");
if (!entity2property2Value.containsKey(entityName)
|| !entity2property2Value.get(entityName).containsKey(STIMULI)
|| !entity2property2Value.get(entityName).get(STIMULI).contains(sourceName)) {
appendToProperty(entityName, STIMULI, ENTITY_ID_REF, sourceName);
}
} else {
insertEntity(sourceName, "");
}
insertEntityInstance(instStmt, sourceName, sourceInstance);
insertEntityInstance(instStmt, entityName, entityInstance);
if (eventType.equalsIgnoreCase("tag")) {
continue;
}
if (newTimestamp > currentTimestamp) {
currentSQCNR = 0;
currentTimestamp = newTimestamp;
} else {
currentSQCNR++;
}
insertEventType(eventType);
// keep track of current active process/runnable per core for signal accesses
if (BTFEntityType.PROCESS.isTraceAlias(entityType) && "C".equals(entity2Type.getOrDefault(sourceName, ""))) {
if (eventType.equalsIgnoreCase(START)) {
actCore2Process.put(sourceName, entityName);
} else if (eventType.equalsIgnoreCase(TERMINATE)) {
actCore2Process.remove(sourceName);
}
}
if (BTFEntityType.RUNNABLE.isTraceAlias(entityType)
&& BTFEntityType.PROCESS.isTraceAlias(entity2Type.getOrDefault(sourceName, ""))
&& actCore2Process.containsValue(sourceName)) {
if (eventType.equalsIgnoreCase(START)) {
actProcess2Runnable.put(sourceName, entityName);
} else if (eventType.equalsIgnoreCase(TERMINATE)) {
actProcess2Runnable.remove(sourceName);
}
}
if (entityType.equals("SIG")
&& BTFEntityType.PROCESS.isTraceAlias(entity2Type.getOrDefault(sourceName, ""))
&& actProcess2Runnable.containsKey(sourceName)) {
final String rName = actProcess2Runnable.get(sourceName);
if (eventType.equalsIgnoreCase("read")
&& (!entity2property2Value.containsKey(rName)
|| !entity2property2Value.get(rName).containsKey(READ_SIGNALS)
|| !entity2property2Value.get(rName).get(READ_SIGNALS).contains(entityName))) {
appendToProperty(rName, READ_SIGNALS, ENTITY_ID_REF, entityName);
} else if (eventType.equalsIgnoreCase("write")
&& (!entity2property2Value.containsKey(rName)
|| !entity2property2Value.get(rName).containsKey(WRITTEN_SIGNALS)
|| !entity2property2Value.get(rName).get(WRITTEN_SIGNALS).contains(entityName))) {
appendToProperty(rName, WRITTEN_SIGNALS, ENTITY_ID_REF, entityName);
}
}
final List<Object> parameters = Arrays.asList(currentTimestamp, currentSQCNR, entityName2Id.get(entityName),
entityInstance, entityName2Id.get(sourceName), sourceInstance, eventTypeName2Id.get(eventType), null);
if (value.isPresent()) {
parameters.set(parameters.size()-1, value.get());
}
con.setPreparedParameters(evStmt, parameters);
evStmt.addBatch();
}
} else {
if (line.toLowerCase().startsWith("#timescale")) {
con.insertMetaInformation("timeBase", line.substring(11));
} else if (line.startsWith("#simulation_duration")) {
setProperty("SIM", "simulationDuration", "time", line.substring(21, line.lastIndexOf(' ')));
}
}
if ((currentLineNumber & 0xFF) == 0) {
lineLengthSum += line.length();
if ((currentLineNumber & 0xFFFF) == 0) {
avgLineLength = ((avgLineLength + (lineLengthSum >> 8)) >> 1);
lineLengthSum = 0;
final long numberOfLines = fileSize / avgLineLength;
final int worked = (int)Math.min(10_000, ((currentLineNumber * 10_000) / numberOfLines));
if (readingBTFProgress < worked) {
readingBTFMonitor.worked(worked - readingBTFProgress);
readingBTFProgress = worked;
}
}
if (currentBatchSize > maxBatchSize) {
con.executeBatchStatements(instStmt, evStmt);
currentBatchSize = 0;
}
}
}
updateEntityTypes(updEntStmt);
executePropertyInsertStatements(propStmt);
readingBTFMonitor.done();
con.executeBatchStatements(updEntStmt, instStmt, evStmt, propStmt);
} catch (final IOException | SQLException e) {
throw new InvocationTargetException(e);
}
}
private final Map<String, String> entity2Type = new LinkedHashMap<>();
private final Set<String> entityTypes = new HashSet<>();
private final Map<String, Long> entityTypeName2Id = new LinkedHashMap<>();
private final Map<String, Long> entityName2Id = new LinkedHashMap<>();
private void insertEntity(final String name, final String entityType) throws SQLException {
if ((!entityType.isEmpty()) && entityTypes.add(entityType)) {
final long entityTypeId = con.immediateInsertEntityType(entityType);
entityTypeName2Id.put(entityType, entityTypeId);
}
if (!entity2Type.containsKey(name)) {
final long entityId = con.immediateInsertEntity(name, -1);
entityName2Id.put(name, entityId);
}
entity2Type.compute(name, (k, v) -> ((v == null) || v.isEmpty()) ? entityType : v);
}
private void updateEntityTypes(final PreparedStatement updEStmt) throws SQLException {
for (final Entry<String, String> entry : entity2Type.entrySet()) {
if (entry.getValue() == null || entry.getValue().isEmpty()) {
continue;
}
final String entityName = entry.getKey();
final long entityType = entityTypeName2Id.get(entry.getValue());
con.setPreparedParameters(updEStmt, Arrays.asList(entityType, entityName));
updEStmt.addBatch();
}
}
private final Map<String, Set<Integer>> instNames = new HashMap<>();
private void insertEntityInstance(final PreparedStatement stmt, final String name, final int inst) throws SQLException {
final Set<Integer> instSet = instNames.computeIfAbsent(name, k -> new HashSet<>());
if (instSet.add(inst)) {
con.setPreparedParameters(stmt, Arrays.asList(entityName2Id.get(name), inst));
stmt.addBatch();
}
}
private final Set<String> eventTypes = new HashSet<>();
private final Map<String, Long> eventTypeName2Id = new LinkedHashMap<>();
private void insertEventType(final String name) throws SQLException {
if (eventTypes.add(name)) {
final long eventTypeId = con.immediateInsertEventType(name);
eventTypeName2Id.put(name, eventTypeId);
}
}
private void appendToProperty(final String entityName, final String propertyName, final String propertyType,
final String propertyValue) {
setProperty(entityName, propertyName, propertyType, propertyValue, true);
}
private void setProperty(final String entityName, final String propertyName, final String propertyType,
final String propertyValue) {
setProperty(entityName, propertyName, propertyType, propertyValue, false);
}
private final Map<String, Map<String, List<String>>> entity2property2Value = new LinkedHashMap<>();
private final Map<String, String> properties = new LinkedHashMap<>();
private void setProperty(final String entityName, final String propertyName, final String propertyType,
final String propertyValue, final boolean append) {
properties.putIfAbsent(propertyName, propertyType);
final Map<String, List<String>> property2Value = entity2property2Value.computeIfAbsent(entityName, k -> new LinkedHashMap<>());
final List<String> values = property2Value.computeIfAbsent(propertyName, k -> new ArrayList<>());
if (!append) {
values.clear();
}
values.add(propertyValue);
}
private void executePropertyInsertStatements(final Statement stmt) throws SQLException {
final List<String> statements = new ArrayList<>();
properties.forEach((propertyName, propertyType) -> statements.add("INSERT INTO property(name, type) VALUES('"//
+ propertyName + "', '" + propertyType + "');"));
entity2property2Value.forEach((entityName, property2Value) -> property2Value.forEach((propertyName, propertyValue) -> {
final List<String> propertyValueStrings = new ArrayList<>();
final String propertyType = properties.get(propertyName);
if (propertyType.endsWith("IdRef")) {
final String tableName = propertyType.substring(0, propertyType.lastIndexOf("IdRef"));
propertyValue.forEach(pv -> propertyValueStrings.add("(SELECT id FROM " + tableName + " WHERE name = '" + pv + "')"));
} else {
propertyValueStrings.add("'" + String.join(", ", propertyValue) + "'");
}
for (int i = 0; i < propertyValueStrings.size(); i++) {
statements.add("INSERT INTO propertyValue(entityId, propertyId, sqcnr, value) VALUES("//
+ "(SELECT id FROM entity WHERE name = '" + entityName + "'), "//
+ "(SELECT id FROM property WHERE name = '" + propertyName + "'), "//
+ i + ","//
+ propertyValueStrings.get(i) + ");");
}
}));
for (final String st : statements) {
stmt.addBatch(st);
}
}
}