| /** |
| ******************************************************************************** |
| * 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); |
| } |
| } |
| |
| } |