MetrixFuzzer.java
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.google.common.collect.Range;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.datasource.DataSourceUtil;
import com.powsybl.computation.ComputationManager;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.contingency.ContingenciesProvider;
import com.powsybl.contingency.dsl.GroovyDslContingenciesProvider;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.serde.NetworkSerDe;
import com.powsybl.metrix.integration.DefaultNetworkSourceImpl;
import com.powsybl.metrix.integration.Metrix;
import com.powsybl.metrix.integration.MetrixAppLogger;
import com.powsybl.metrix.integration.MetrixRunParameters;
import com.powsybl.metrix.integration.NetworkSource;
import com.powsybl.metrix.integration.io.ResultListener;
import com.powsybl.metrix.integration.metrix.MetrixAnalysis;
import com.powsybl.metrix.integration.metrix.MetrixAnalysisResult;
import com.powsybl.metrix.mapping.ComputationRange;
import com.powsybl.metrix.mapping.DataTableStore;
import com.powsybl.metrix.mapping.EquipmentGroupTimeSeriesWriterObserver;
import com.powsybl.metrix.mapping.EquipmentTimeSeriesWriterObserver;
import com.powsybl.metrix.mapping.MappingParameters;
import com.powsybl.metrix.mapping.NetworkPointWriter;
import com.powsybl.metrix.mapping.TimeSeriesDslLoader;
import com.powsybl.metrix.mapping.TimeSeriesMapper;
import com.powsybl.metrix.mapping.TimeSeriesMapperObserver;
import com.powsybl.metrix.mapping.TimeSeriesMapperParameters;
import com.powsybl.metrix.mapping.TimeSeriesMappingConfig;
import com.powsybl.metrix.mapping.TimeSeriesMappingConfigCsvWriter;
import com.powsybl.metrix.mapping.TimeSeriesMappingConfigTableLoader;
import com.powsybl.metrix.mapping.TimeSeriesMappingLogger;
import com.powsybl.metrix.mapping.timeseries.FileSystemTimeSeriesStore;
import com.powsybl.metrix.mapping.timeseries.InMemoryTimeSeriesStore;
import com.powsybl.timeseries.TimeSeries;
import com.powsybl.timeseries.TimeSeriesIndex;
import com.powsybl.tools.ToolRunningContext;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.zip.ZipOutputStream;
public class MetrixFuzzer {
private static Path timeFilePath;
private static Path mappingFilePath;
private static Path networkFilePath;
private static Path contingencyFilePath;
private static Path configFilePath;
private static Path actionFilePath;
private static Path outputFilePath;
private static Path tempDirPath;
public static void fuzzerInitialize() {
try {
timeFilePath = Files.createTempFile("fuzz-", "-fuzz");
timeFilePath.toFile().deleteOnExit();
mappingFilePath = Files.createTempFile("fuzz-", "-fuzz");
mappingFilePath.toFile().deleteOnExit();
networkFilePath = Files.createTempFile("fuzz-", "-fuzz");
networkFilePath.toFile().deleteOnExit();
contingencyFilePath = Files.createTempFile("fuzz-", "-fuzz");
contingencyFilePath.toFile().deleteOnExit();
configFilePath = Files.createTempFile("fuzz-", "-fuzz");
configFilePath.toFile().deleteOnExit();
actionFilePath = Files.createTempFile("fuzz-", "-fuzz");
actionFilePath.toFile().deleteOnExit();
outputFilePath = Files.createTempFile("fuzz-", "-fuzz");
outputFilePath.toFile().deleteOnExit();
tempDirPath = Files.createTempDirectory("fuzz-");
tempDirPath.toFile().deleteOnExit();
} catch (Throwable ignored) {
timeFilePath = null;
mappingFilePath = null;
networkFilePath = null;
outputFilePath = null;
tempDirPath = null;
}
}
public static void fuzzerTestOneInput(FuzzedDataProvider data) throws IOException {
if (tempDirPath == null) {
return;
}
try {
// Get random values
int firstVariant = data.consumeInt();
int maxVariantCount = data.consumeInt();
int variantCount = data.consumeInt();
int chunkSize = data.consumeInt();
// Get random versions in TreeSet
TreeSet<Integer> versions = new TreeSet<Integer>();
versions.add(data.consumeInt());
// Randomise file content
FileWriter fw = new FileWriter(timeFilePath.toFile());
fw.write("Time;Version;ts1;ts2\n");
Long minTs = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toEpochSecond();
for (Integer i = 1; i <= data.consumeInt(2, 5); i++) {
// Safe range for instance epoch second
Long ts = data.consumeLong(minTs, 32503680000L);
ZonedDateTime zdt = Instant.ofEpochSecond(ts + i).atZone(ZoneOffset.UTC);
fw.write(zdt.toString() + ";1;" + ((double) i) + ";" + (i + 0.1) + "\n");
}
fw.close();
fw = new FileWriter(mappingFilePath.toFile());
fw.write(data.consumeString(data.remainingBytes() / 5));
fw.close();
fw = new FileWriter(configFilePath.toFile());
fw.write(data.consumeString(data.remainingBytes() / 4));
fw.close();
fw = new FileWriter(actionFilePath.toFile());
fw.write(data.consumeString(data.remainingBytes() / 3));
fw.close();
fw = new FileWriter(contingencyFilePath.toFile());
fw.write(data.consumeString(data.remainingBytes() / 2));
fw.close();
fw = new FileWriter(networkFilePath.toFile());
fw.write(data.consumeRemainingAsString());
fw.close();
// Prepare different objects from random files
ZipOutputStream logArchive =
new ZipOutputStream(new FileOutputStream(outputFilePath.toFile()));
PrintStream outputStream = new PrintStream(outputFilePath.toFile());
ComputationManager computationManager = LocalComputationManager.getDefault();
ToolRunningContext context =
new ToolRunningContext(
outputStream,
outputStream,
FileSystems.getDefault(),
computationManager,
computationManager);
InMemoryTimeSeriesStore store = new InMemoryTimeSeriesStore();
store.importTimeSeries(Collections.singletonList(timeFilePath));
Network network =
NetworkSerDe.read(
Object.class.getResourceAsStream(networkFilePath.getFileName().toString()));
NetworkSource networkSource =
new DefaultNetworkSourceImpl(networkFilePath, computationManager);
ContingenciesProvider contingenciesProvider =
new GroovyDslContingenciesProvider(contingencyFilePath);
Reader metrixDslReader = Files.newBufferedReader(configFilePath, StandardCharsets.UTF_8);
Reader remedialActionsReader =
Files.newBufferedReader(actionFilePath, StandardCharsets.UTF_8);
MappingParameters mappingParameters = MappingParameters.load();
ComputationRange computationRange =
new ComputationRange(store.getTimeSeriesDataVersions(), firstVariant, maxVariantCount);
TimeSeriesMappingConfig config = null;
TimeSeriesDslLoader dslLoader = null;
try (Reader reader = Files.newBufferedReader(mappingFilePath, StandardCharsets.UTF_8)) {
dslLoader = new TimeSeriesDslLoader(reader, mappingFilePath.getFileName().toString());
config =
dslLoader.load(
network, mappingParameters, store, new DataTableStore(), computationRange);
}
TimeSeriesMappingConfigCsvWriter csvWriter =
new TimeSeriesMappingConfigCsvWriter(
config, network, store, computationRange, mappingParameters.getWithTimeSeriesStats());
csvWriter.writeMappingCsv(outputFilePath);
TimeSeriesMappingLogger logger = new TimeSeriesMappingLogger();
List<TimeSeriesMapperObserver> observers = new ArrayList<>();
FileSystemTimeSeriesStore resultStore = new FileSystemTimeSeriesStore(tempDirPath);
DataSource dataSource = DataSourceUtil.createDataSource(tempDirPath, null);
observers.add(new NetworkPointWriter(network, dataSource));
TimeSeriesIndex index =
new TimeSeriesMappingConfigTableLoader(config, store).checkIndexUnicity();
int lastPoint = Math.min(firstVariant + maxVariantCount, index.getPointCount()) - 1;
Range<Integer> range = Range.closed(firstVariant, lastPoint);
observers.add(
new EquipmentTimeSeriesWriterObserver(
network, config, maxVariantCount, range, tempDirPath));
observers.add(
new EquipmentGroupTimeSeriesWriterObserver(
network, config, maxVariantCount, range, tempDirPath));
TimeSeriesMapperParameters parameters =
new TimeSeriesMapperParameters(
(NavigableSet<Integer>) store.getTimeSeriesDataVersions(),
range,
true,
true,
false,
mappingParameters.getToleranceThreshold());
ResultListener listener =
new ResultListener() {
@Override
public void onChunkResult(
int version, int chunk, List<TimeSeries> timeSeriesList, Network networkPoint) {
resultStore.importTimeSeries(timeSeriesList, version);
}
@Override
public void onEnd() {
// Do nothing
}
};
MetrixAppLogger metrixLogger =
new MetrixAppLogger() {
@Override
public void log(String message, Object... args) {
// Do nothing
}
@Override
public MetrixAppLogger tagged(String tag) {
return this;
}
};
MetrixAnalysis metrixAnalysis =
new MetrixAnalysis(
networkSource,
dslLoader,
metrixDslReader,
remedialActionsReader,
contingenciesProvider,
store,
new DataTableStore(),
metrixLogger,
computationRange);
MetrixAnalysisResult analysisResult = metrixAnalysis.runAnalysis("extern tool");
// Fuzz mapper
TimeSeriesMapper mapper = new TimeSeriesMapper(config, parameters, network, logger);
mapper.mapToNetwork(store, observers);
// Fuzz Metrix
Metrix metrix =
new Metrix(
remedialActionsReader,
store,
resultStore,
logArchive,
context,
metrixLogger,
analysisResult);
MetrixRunParameters runParams =
new MetrixRunParameters(computationRange, chunkSize, true, true, false, false, false);
metrix.run(runParams, listener, "Fuzz");
} catch (PowsyblException | IllegalArgumentException | IllegalStateException | IOException e) {
// Ignore known exceptions
} catch (NullPointerException e) {
// Capture known NPE from malformed JSON
if (!isExpected(e)) {
throw e;
}
}
}
private static boolean isExpected(Throwable e) {
String[] expectedString = {
"java.util.Objects.requireNonNull",
"Cannot invoke \"String.hashCode()\"",
"Name is null",
"Cannot invoke \"com.fasterxml.jackson.databind.JsonNode.get(String)\""
};
for (String expected : expectedString) {
if (e.toString().contains(expected)) {
return true;
}
for (StackTraceElement ste : e.getStackTrace()) {
if (ste.toString().contains(expected)) {
return true;
}
}
}
return false;
}
}