diff --git a/docs/develop/extend-metrics.md b/docs/develop/extend-metrics.md index 43542dd7e..76fffcc92 100644 --- a/docs/develop/extend-metrics.md +++ b/docs/develop/extend-metrics.md @@ -46,7 +46,6 @@ The following gives you an examples on how to work with the `data` parameter: } @Override - @Nonnull public Model createMetricModel(StresstestMetadata task, Map> data) { for (String queryID : task.queryIDS()) { // This list contains every query execution statistics of one query from diff --git a/docs/develop/extend-queryhandling.md b/docs/develop/extend-queryhandling.md index f4bb6150f..0a59c8e7c 100644 --- a/docs/develop/extend-queryhandling.md +++ b/docs/develop/extend-queryhandling.md @@ -66,7 +66,7 @@ implements the following methods: public class MyQuerySource extends QuerySource { public MyQuerySource(String filepath) { // your constructor - // filepath is the value, specified in the "location"-key inside the configuration file + // filepath is the value, specified in the "path"-key inside the configuration file } @Override diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md index bec58f4f1..ad3ed5613 100644 --- a/docs/usage/configuration.md +++ b/docs/usage/configuration.md @@ -316,14 +316,14 @@ For example, instead of: ```yaml storages: - - className: "org.aksw.iguana.rp.storage.impl.NTFileStorage" + - className: "org.aksw.iguana.rp.storage.impl.RDFFileStorage" ``` you can use the shortname NTFileStorage: ```yaml storages: - - className: "NTFileStorage" + - className: "RDFFileStorage" ``` diff --git a/example-suite.yml b/example-suite.yml index eef144436..e81ff4b7e 100644 --- a/example-suite.yml +++ b/example-suite.yml @@ -7,6 +7,7 @@ connections: user: "dba" password: "dba" endpoint: "http://localhost:8890/sparql" + dataset: DatasetName - name: "Virtuoso6" user: "dba" password: "dba" @@ -19,49 +20,63 @@ connections: updateEndpoint: "http://localhost:3030/ds/update" tasks: - - className: "org.aksw.iguana.cc.tasks.impl.Stresstest" - configuration: - # 1 hour (time Limit is in ms) - timeLimit: 360000 - # warmup is optional - warmup: - # 1 minutes (is in ms) - timeLimit: 600000 - workers: - - threads: 1 - className: "HttpGetWorker" - queries: - location: "queries_warmup.txt" - timeOut: 180000 - workers: - - threads: 16 - className: "HttpGetWorker" - queries: - location: "queries_easy.txt" - timeOut: 180000 - - threads: 4 - className: "HttpGetWorker" - queries: - location: "queries_complex.txt" - fixedLatency: 100 - gaussianLatency: 50 - parameterName: "query" - responseType: "application/sparql-results+json" - -# both are optional and can be used to load and start as well as stop the connection before and after every task -preScriptHook: "./triplestores/{{connection}}/start.sh {{dataset.file}} {{dataset.name}} {{taskID}}" -postScriptHook: "./triplestores/{{connection}}/stop.sh" - -#optional otherwise the same metrics will be used as default -metrics: - - className: "QMPH" - - className: "QPS" - - className: "NoQPH" - - className: "AvgQPS" - - className: "NoQ" + # 1 hour (time Limit is in ms) + - type: stresstest + warmupWorkers: + # 1 minutes (is in ms) + - type: SPARQLProtocolWorker + number: 16 + queries: + path: "/home/bigerl/IdeaProjects/IGUANA/LICENSE" + timeout: 0.02s + connection: Virtuoso7 + completionTarget: + duration: 1000s + workers: + - type: "SPARQLProtocolWorker" + number: 16 + queries: + path: "/home/bigerl/IdeaProjects/IGUANA/LICENSE" + timeout: 3m + connection: Virtuoso7 + completionTarget: + duration: 1000s + requestType: get query + - number: 4 + type: "SPARQLProtocolWorker" + connection: Virtuoso7 + completionTarget: + duration: 1000s + queries: + path: "/home/bigerl/IdeaProjects/IGUANA/LICENSE" + timeout: 100s + acceptHeader: "application/sparql-results+json" + - type: stresstest + workers: + - type: "SPARQLProtocolWorker" + connection: Virtuoso7 + number: 16 + requestType: get query + queries: + path: "/home/bigerl/IdeaProjects/IGUANA/LICENSE" + timeout: 180s + completionTarget: + duration: 1000s + - number: 4 + requestType: get query + connection: Virtuoso7 + completionTarget: + duration: 1000s + type: "SPARQLProtocolWorker" + queries: + path: "/home/bigerl/IdeaProjects/IGUANA/LICENSE" + timeout: 100s + parseResults: true + acceptHeader: "application/sparql-results+json" #optional otherwise an nt file will be used storages: - - className: "NTFileStorage" + - type: "RDF file" + path: "some.ttl" #configuration: #fileName: YOUR_RESULT_FILE_NAME.nt \ No newline at end of file diff --git a/pom.xml b/pom.xml index bcb8e56ee..755e70146 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 17 17 - 2.17.1 + 2.19.0 @@ -78,27 +78,11 @@ jena-querybuilder ${jena.version} - - junit - junit - 4.13.1 - test - org.apache.httpcomponents httpclient 4.5.13 - - commons-configuration - commons-configuration - 1.10 - - - org.apache.commons - commons-exec - 1.3 - org.apache.logging.log4j log4j-slf4j-impl @@ -119,25 +103,15 @@ log4j-1.2-api ${log4j.version} - - org.simpleframework - simple - 5.1.6 - - - org.reflections - reflections - 0.9.9 - - - commons-codec - commons-codec - 1.15 - com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.11.2 + 2.12.5 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.12.5 com.networknt @@ -161,17 +135,47 @@ 5.9.2 test - - org.junit.vintage - junit-vintage-engine - 5.9.2 - test - com.opencsv opencsv 5.7.1 + + org.lz4 + lz4-pure-java + 1.8.0 + + + org.apache.hbase + hbase-common + 2.5.5 + + + com.beust + jcommander + 1.82 + + + com.github.tomakehurst + wiremock-jre8-standalone + 2.35.0 + test + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + org.springframework.data + spring-data-commons + 3.1.2 + + + org.springframework + spring-context + 6.0.11 + @@ -194,6 +198,16 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + -Xmx16384M + + + + org.apache.maven.plugins maven-shade-plugin @@ -210,7 +224,8 @@ - + org.aksw.iguana.cc.controller.MainController diff --git a/schema/iguana-schema.json b/schema/iguana-schema.json index 71b45a045..d2e12eff2 100644 --- a/schema/iguana-schema.json +++ b/schema/iguana-schema.json @@ -1,367 +1,389 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/root", "definitions": { - "connection": { + "root": { + "title": "root", "type": "object", + "additionalProperties": false, "properties": { - "endpoint": {"type": "string"}, - "updateEndpoint": {"type": "string"}, - "user": {"type": "string"}, - "password": {"type": "string"}, - "version": {"type": "string"} + "datasets": { + "type": "array", + "items": { + "$ref": "#/definitions/Dataset" + }, + "minItems": 1 + }, + "connections": { + "type": "array", + "items": { + "$ref": "#/definitions/Connection" + }, + "minItems": 1 + }, + "tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/Task" + }, + "minItems": 1 + }, + "storages": { + "type": "array", + "items": { + "$ref": "#/definitions/Storage" + }, + "minItems": 1 + }, + "responseBodyProcessors": { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseBodyProcessor" + } + }, + "metrics": { + "type": "array", + "items": { + "$ref": "#/definitions/Metric" + } + } }, - "required": ["endpoint"] + "required": [ + "connections", + "datasets", + "storages", + "tasks" + ] }, - "queries": { + "Connection": { "type": "object", + "additionalProperties": false, "properties": { - "location": {"type": "string"}, - "format": { - "oneOf": [ - {"type": "string"}, - { - "type": "object", - "properties": { - "separator": {"type": "string"} - } - } - ] - }, - "caching": {"type": "boolean"}, - "order": { - "oneOf": [ - {"type": "string"}, - { - "type": "object", - "properties": { - "random": { - "type": "object", - "properties": { - "seed": {"type": "integer"} - }, - "required": ["seed"] - } - } - } - ] - }, - "pattern": { - "type": "object", - "properties": { - "endpoint": {"type": "string"}, - "outputFolder": {"type": "string"}, - "limit": {"type": "integer"} - }, - "required": ["endpoint"] + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "endpoint": { + "type": "string", + "format": "uri" + }, + "updateEndpoint": { + "type": "string", + "format": "uri" + }, + "authentication": { + "$ref": "#/definitions/Authentication" + }, + "updateAuthentication": { + "$ref": "#/definitions/Authentication" }, - "lang": {"type": "string"} + "dataset": { + "type": "string" + } }, - "required": ["location"] + "required": [ + "endpoint", + "name" + ], + "title": "Connection" }, - - "warmup": { + "Authentication": { "type": "object", + "additionalProperties": false, "properties": { - "timeLimit": {"type": "integer"}, - "workers": { - "type": "array", - "items": { - "oneOf": [{"$ref": "#/definitions/AbstractWorker"}] - } + "user": { + "type": "string" + }, + "password": { + "type": "string" } }, - "required": ["workers", "timeLimit"] + "required": [ + "password", + "user" + ], + "title": "Authentication" }, - - "stresstest": { + "Dataset": { "type": "object", + "additionalProperties": false, "properties": { - "timeLimit": {"type": "integer"}, - "noOfQueryMixes": {"type": "integer"}, - "warmup": {"$ref": "#/definitions/warmup"}, - "workers": { - "type": "array", - "items": { - "oneOf": [{"$ref": "#/definitions/AbstractWorker"}] - } + "name": { + "type": "string" + }, + "file": { + "type": "string" } }, - "required": ["workers"] + "required": [ + "name" + ], + "title": "Dataset" }, - - "AbstractWorker": { + "Metric": { "type": "object", + "additionalProperties": false, "properties": { - "className": {"type": "string"} + "type": { + "type": "string", + "enum": [ "AES", "AvgQPS", "EachQuery", "NoQ", "NoQPH", "PAvgQPS", "PQPS", "QMPH", "QPS" ] + }, + "penalty": { + "type": "integer", + "minimum": 0 + } }, - "allOf": [ - { - "if": { - "properties": { - "className" : { - "oneOf": [ {"const": "SPARQLWorker"},{"const": "org.aksw.iguana.cc.worker.impl.SPARQLWorker"}] - } - } - }, - "then": { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "responseType": {"type": "string"}, - "parameterName": {"type": "string"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"}, - "required": ["className", "threads", "queries"] - } + "required": [ + "type" + ], + "title": "Metric" + }, + "ResponseBodyProcessor": { + "type": "object", + "additionalProperties": false, + "properties": { + "contentType": { + "type": "string" }, - { - "if": { - "properties": { - "className" : { - "oneOf": [{"const": "UPDATEWorker"},{"const": "org.aksw.iguana.cc.worker.impl.UPDATEWorker"}] - } - } - }, - "then": { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "timerStrategy": {"type": "string"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"}, - "required": ["className", "threads", "queries"] - } + "threads": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "contentType", + "threads" + ], + "title": "ResponseBodyProcessor" + }, + "Storage": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/CSVStorage" }, + { "$ref": "#/definitions/RDFFileStorage" }, + { "$ref": "#/definitions/TriplestoreStorage" } + ], + "title": "Storage" + }, + "CSVStorage": { + "type": "object", + "unevaluatedProperties": false, + "properties": { + "type": { + "type": "string", + "const": "csv file" }, - { - "if": { - "properties": { - "className" : { - "oneOf": [{"const": "MultipleCLIInputWorker"}, {"const": "org.aksw.iguana.cc.worker.impl.MultipleCLIInputWorker"}] - } - }}, - "then": { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "queryError": {"type": "string"}, - "queryFinished": {"type": "string"}, - "initFinished": {"type": "string"}, - "numberOfProcesses": {"type": "integer"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"}, - "required": ["className", "threads", "queryError", "queryFinished", "initFinished", "queries"] - } + "directory": { + "type": "string" + } + }, + "required": [ + "type", + "directory" + ], + "title": "CSVStorage" + }, + "RDFFileStorage": { + "type": "object", + "unevaluatedProperties": false, + "properties": { + "type": { + "type": "string", + "const": "rdf file" }, - { - "if": { - "properties": { - "className" : { - "oneOf": [{"const": "CLIInputWorker"}, {"const": "org.aksw.iguana.cc.worker.impl.CLIInputWorker"}] - } - } - }, - "then": { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "queryError": {"type": "string"}, - "queryFinished": {"type": "string"}, - "initFinished": {"type": "string"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"}, - "required": ["className", "threads", "queryError", "queryFinished", "initFinished", "queries"] - } + "path": { + "type": "string" + } + }, + "required": [ + "type", + "path" + ], + "title": "RDFFileStorage" + }, + "TriplestoreStorage": { + "type": "object", + "unevaluatedProperties": false, + "properties": { + "type": { + "type": "string", + "const": "triplestore" }, - { - "if": { - "properties": { - "className": { - "oneOf": [{"const": "CLIPrefixWorker"}, {"const": "org.aksw.iguana.cc.worker.impl.CLIPrefixWorker"}] - } - } - }, - "then": { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "numberOfProcesses": {"type": "integer"}, - "queryError": {"type": "string"}, - "queryFinished": {"type": "string"}, - "initFinished": {"type": "string"}, - "querySuffix": {"type": "string"}, - "queryPrefix": {"type": "string"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"}, - "required": [ - "className", - "threads", - "queryError", - "queryFinished", - "initFinished", - "queryPrefix", - "querySuffix", - "queries" - ] - } + "endpoint": { + "type": "string", + "format": "uri" }, - { - "if": { - "properties": { - "className": { - "oneOf": [{"const": "MultipleCLIInputFileWorker"}, {"const": "org.aksw.iguana.cc.worker.impl.MultipleCLIInputFileWorker"}] - } - } - }, - "then": { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "queryError": {"type": "string"}, - "queryFinished": {"type": "string"}, - "initFinished": {"type": "string"}, - "directory": {"type": "string"}, - "numberOfProcesses": {"type": "integer"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"}, - "required": [ - "className", - "threads", - "directory", - "queryError", - "queryFinished", - "initFinished", - "queries" - ] - } + "user": { + "type": "string" }, - { - "if": { - "properties": { - "className": { - "oneOf": [{"const": "CLIInputFileWorker"}, {"const": "org.aksw.iguana.cc.worker.impl.CLIInputFileWorker"}] - } - } - }, - "then": { - "allOf": [ - { - "properties": { - "className": {"type": "string"}, - "threads": {"type": "integer"}, - "timeOut": {"type": "integer"}, - "fixedLatency": {"type": "integer"}, - "gaussianLatency": {"type": "integer"}, - "queryError": {"type": "string"}, - "queryFinished": {"type": "string"}, - "initFinished": {"type": "string"}, - "directory": {"type": "string"}, - "queries": {"$ref": "#/definitions/queries"} - }, - "additionalProperties": {"type": "null"} - }, - { - "required": [ - "className", - "threads", - "directory", - "queryError", - "queryFinished", - "initFinished", - "queries" - ] - } - ] - } + "password": { + "type": "string" + }, + "baseUri": { + "type": "string", + "format": "uri" } - ] + }, + "required": [ + "type", + "endpoint" + ], + "title": "TriplestoreStorage" }, - - "task": { + "Task": { "type": "object", + "oneOf": [ { "$ref": "#/definitions/Stresstest" } ], + "title": "Task" + }, + "Stresstest": { + "type": "object", + "unevaluatedProperties": false, "properties": { - "className": {"type": "string"}, - "configuration": { - "oneOf": [{"$ref": "#/definitions/stresstest"}] + "type": { + "type": "string", + "const": "stresstest" + }, + "warmupWorkers": { + "type": "array", + "items": { + "$ref": "#/definitions/Worker" + } + }, + "workers": { + "type": "array", + "items": { + "$ref": "#/definitions/Worker" + }, + "minItems": 1 } }, - "required": ["className", "configuration"] + "required": [ + "type", + "workers" + ], + "title": "Stresstest" }, - - "genericClassObject": { + "Worker": { + "type": "object", + "oneOf": [ { "$ref": "#/definitions/SPARQLWorker" } ], + "title": "Worker" + }, + "SPARQLWorker" : { "type": "object", + "unevaluatedProperties": false, "properties": { - "className": {"type": "string"}, - "configuration": { - "type": "object" + "type": { + "type": "string", + "const": "SPARQLProtocolWorker" + }, + "number": { + "type": "integer", + "minimum": 1 + }, + "requestType": { + "type": "string", + "enum": [ "post query", "get query", "post url-enc query", "post url-enc update", "post update" ] + }, + "queries": { + "$ref": "#/definitions/Queries" + }, + "timeout": { + "type": "string" + }, + "connection": { + "type": "string" + }, + "completionTarget": { + "$ref": "#/definitions/CompletionTarget" + }, + "parseResults": { + "type": "boolean" + }, + "acceptHeader": { + "type": "string" } }, - "required": ["className"] - } - }, - - "type": "object", - "properties": { - "connections": { - "type": "array", - "items": { - "$ref": "#/definitions/connection" - } + "required": [ + "type", + "completionTarget", + "connection", + "queries", + "timeout" + ], + "title": "SPARQLWorker" }, - "datasets": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name" : {"type": "string"} - }, - "required": ["name"] - } + "CompletionTarget": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/TimeLimit" }, + { "$ref": "#/definitions/QueryMixes" } + ], + "title": "CompletionTarget" }, - "tasks": { - "type": "array", - "items": { - "$ref":"#/definitions/task" - } + "TimeLimit": { + "properties": { + "duration": { + "type": "string" + } + }, + "title": "TimeLimit", + "type": "object", + "unevaluatedProperties": false, + "required": [ + "duration" + ] }, - "preScriptHook": {"type": "string"}, - "postScriptHook": {"type": "string"}, - "metrics": { - "type": "array", - "items": { - "$ref": "#/definitions/genericClassObject" - } + "QueryMixes": { + "properties": { + "number": { + "type": "integer", + "minimum": 1 + } + }, + "title": "QueryMixes", + "type": "object", + "unevaluatedProperties": false, + "required": [ + "number" + ] }, - "storages": { - "type": "array", - "items": { - "$ref": "#/definitions/genericClassObject" - } + "Queries": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "format": { + "type": "string", + "enum": [ "one-per-line", "separator", "folder" ] + }, + "separator": { + "type": "string" + }, + "caching": { + "type": "boolean" + }, + "order": { + "type": "string", + "enum": [ "random", "linear" ] + }, + "seed": { + "type": "integer" + }, + "lang": { + "type": "string", + "enum": [ "", "SPARQL" ] + } + }, + "required": [ + "path" + + ], + "title": "Queries" } } } diff --git a/src/main/java/org/aksw/iguana/cc/config/CONSTANTS.java b/src/main/java/org/aksw/iguana/cc/config/CONSTANTS.java deleted file mode 100644 index 4b3c7ebca..000000000 --- a/src/main/java/org/aksw/iguana/cc/config/CONSTANTS.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.aksw.iguana.cc.config; - -/** - * Constants used only by the Core controller - * - * @author f.conrads - * - */ -public class CONSTANTS { - - /** - * The key to set the workerID in the Extra Meta properties - * and the properties name in the final results to get the workerID - */ - public static final String WORKER_ID_KEY = "workerID"; - - /** - * The key to set the workerType in the Extra Meta properties - * and the properties name in the final results to get the workerType - */ - public static final String WORKER_TYPE_KEY = "workerType"; - - /** - * The key to get the timeLimit parameter. - * be aware that timeLimit can be null. - */ - public static final String TIME_LIMIT = "timeLimit"; - - - public static final String NO_OF_QUERY_MIXES = "numberOfQueryMixes"; - - - public static final String WORKER_TIMEOUT_MS = "timeOutMS"; -} diff --git a/src/main/java/org/aksw/iguana/cc/config/ConfigManager.java b/src/main/java/org/aksw/iguana/cc/config/ConfigManager.java deleted file mode 100644 index b0946ba0d..000000000 --- a/src/main/java/org/aksw/iguana/cc/config/ConfigManager.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; - - -/** - * Manages an incoming Configuration and starts the corresponding {@link org.aksw.iguana.cc.config.IguanaConfig} - * - * @author f.conrads - * - */ -public class ConfigManager { - - private Logger LOGGER = LoggerFactory.getLogger(getClass()); - - - /** - * Will receive a JSON or YAML configuration and executes the configuration as an Iguana Suite - * @param configuration - * @param validate checks if error should be thrown if it validates the configuration given the iguana-schema.json schema - */ - public void receiveData(File configuration, Boolean validate) throws IOException { - - IguanaConfig newConfig = IguanaConfigFactory.parse(configuration, validate); - if(newConfig==null){ - return; - } - startConfig(newConfig); - } - - - - /** - * Starts the Config - */ - public void startConfig(IguanaConfig config) { - try { - config.start(); - } catch (IOException e) { - LOGGER.error("Could not start config due to an IO Exception", e); - } - - } - - - -} diff --git a/src/main/java/org/aksw/iguana/cc/config/IguanaConfig.java b/src/main/java/org/aksw/iguana/cc/config/IguanaConfig.java deleted file mode 100644 index e2d16b112..000000000 --- a/src/main/java/org/aksw/iguana/cc/config/IguanaConfig.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.aksw.iguana.cc.config; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.aksw.iguana.cc.config.elements.*; -import org.aksw.iguana.cc.controller.TaskController; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.MetricManager; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.*; -import org.aksw.iguana.cc.tasks.stresstest.storage.StorageManager; -import org.aksw.iguana.commons.script.ScriptExecutor; -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; -import org.aksw.iguana.cc.tasks.stresstest.storage.impl.NTFileStorage; -import org.apache.commons.exec.ExecuteException; -import org.apache.commons.lang3.SerializationUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Gets either a JSON or YAML configuration file using a json schema and will generate - * a SuiteID and ExperimentIDs as well as TaskIDs for it.
- * Afterwards it will start the taskProcessor with all specified tasks - *

- * The following order holds - *
    - *
  1. For each Dataset
  2. - *
  3. For each Connection
  4. - *
  5. For each Task
  6. - *
- * - * Further on executes the pre and post script hooks, before and after a class. - * Following values will be exchanged in the script string {{Connection}} {{Dataset.name}} {{Dataset.file}} {{taskID}} - * - * @author f.conrads - * - */ -public class IguanaConfig { - - private static final Logger LOGGER = LoggerFactory.getLogger(IguanaConfig.class); - - @JsonProperty(required = true) - private List datasets; - @JsonProperty(required = true) - private List connections; - @JsonProperty(required = true) - private List tasks; - @JsonProperty - private String preScriptHook; - @JsonProperty - private String postScriptHook; - @JsonProperty - private List metrics; - @JsonProperty - private List storages; - - private static String suiteID = generateSuiteID(); - - /** - * starts the config - * @throws IOException - * @throws ExecuteException - */ - public void start() throws ExecuteException, IOException { - initResultProcessor(); - TaskController controller = new TaskController(); - //get SuiteID - suiteID = generateSuiteID(); - //generate ExpID - int expID = 0; - - for(DatasetConfig dataset: datasets){ - expID++; - Integer taskID = 0; - for(ConnectionConfig con : connections){ - for(TaskConfig task : tasks) { - taskID++; - String[] args = new String[] {}; - if(preScriptHook!=null){ - LOGGER.info("Executing preScriptHook"); - String execScript = preScriptHook.replace("{{dataset.name}}", dataset.getName()) - .replace("{{connection}}", con.getName()) - .replace("{{connection.version}}", con.getVersion("{{connection.version}}")) - .replace("{{taskID}}", taskID+""); - LOGGER.info("Finished preScriptHook"); - if(dataset.getFile()!=null){ - execScript = execScript.replace("{{dataset.file}}", dataset.getFile()); - } - - ScriptExecutor.execSafe(execScript, args); - } - LOGGER.info("Executing Task [{}/{}: {}, {}, {}]", taskID, task.getName(), dataset.getName(), con.getName(), task.getClassName()); - controller.startTask(new String[]{suiteID, suiteID + "/" + expID, suiteID + "/" + expID + "/" + taskID}, dataset.getName(), SerializationUtils.clone(con), SerializationUtils.clone(task)); - if(postScriptHook!=null){ - String execScript = postScriptHook.replace("{{dataset.name}}", dataset.getName()) - .replace("{{connection}}", con.getName()) - .replace("{{connection.version}}", con.getVersion("{{connection.version}}")) - .replace("{{taskID}}", taskID+""); - if(dataset.getFile()!=null){ - execScript = execScript.replace("{{dataset.file}}", dataset.getFile()); - } - LOGGER.info("Executing postScriptHook {}", execScript); - ScriptExecutor.execSafe(execScript, args); - LOGGER.info("Finished postScriptHook"); - } - } - } - } - - LOGGER.info("Finished benchmark"); - } - - private void initResultProcessor() { - //If storage or metric is empty use default - if(this.storages== null || this.storages.isEmpty()){ - storages = new ArrayList<>(); - StorageConfig config = new StorageConfig(); - config.setClassName(NTFileStorage.class.getCanonicalName()); - storages.add(config); - } - if(this.metrics == null || this.metrics.isEmpty()){ - LOGGER.info("No metrics were set. Using default metrics."); - metrics = new ArrayList<>(); - MetricConfig config = new MetricConfig(); - config.setClassName(QMPH.class.getCanonicalName()); - metrics.add(config); - config = new MetricConfig(); - config.setClassName(QPS.class.getCanonicalName()); - metrics.add(config); - config = new MetricConfig(); - config.setClassName(NoQPH.class.getCanonicalName()); - metrics.add(config); - config = new MetricConfig(); - config.setClassName(AvgQPS.class.getCanonicalName()); - metrics.add(config); - config = new MetricConfig(); - config.setClassName(NoQ.class.getCanonicalName()); - metrics.add(config); - config = new MetricConfig(); - config.setClassName(AggregatedExecutionStatistics.class.getCanonicalName()); - metrics.add(config); - } - - // Create Metrics - // Metrics should be created before the Storages - List metrics = new ArrayList<>(); - for(MetricConfig config : this.metrics) { - metrics.add(config.createMetric()); - } - MetricManager.setMetrics(metrics); - - //Create Storages - List storages = new ArrayList<>(); - for(StorageConfig config : this.storages){ - storages.add(config.createStorage()); - } - StorageManager.getInstance().addStorages(storages); - } - - public static String getSuiteID() { - return suiteID; - } - - private static String generateSuiteID() { - int currentTimeMillisHashCode = Math.abs(Long.valueOf(Instant.now().getEpochSecond()).hashCode()); - return String.valueOf(currentTimeMillisHashCode); - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/config/IguanaConfigFactory.java b/src/main/java/org/aksw/iguana/cc/config/IguanaConfigFactory.java deleted file mode 100644 index 68ec5de87..000000000 --- a/src/main/java/org/aksw/iguana/cc/config/IguanaConfigFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.aksw.iguana.cc.config; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Set; - -/** - * Creates an IguanaConfig from a given JSON or YAML file, and validates the config using a JSON schema file - */ -public class IguanaConfigFactory { - - private static Logger LOGGER = LoggerFactory.getLogger(IguanaConfigFactory.class); - - private static String schemaFile = "iguana-schema.json"; - - public static IguanaConfig parse(File config) throws IOException { - return parse(config, true); - } - - public static IguanaConfig parse(File config, Boolean validate) throws IOException { - if(config.getName().endsWith(".yml") || config.getName().endsWith(".yaml")){ - return parse(config, new YAMLFactory(), validate); - } - else if(config.getName().endsWith(".json")){ - return parse(config, new JsonFactory(), validate); - } - return null; - } - private static IguanaConfig parse(File config, JsonFactory factory) throws IOException { - return parse(config, factory, true); - } - - private static IguanaConfig parse(File config, JsonFactory factory, Boolean validate) throws IOException { - final ObjectMapper mapper = new ObjectMapper(factory); - if(validate && !validateConfig(config, schemaFile, mapper)){ - return null; - } - return mapper.readValue(config, IguanaConfig.class); - } - - private static boolean validateConfig(File configuration, String schemaFile, ObjectMapper mapper) throws IOException { - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - InputStream is = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(schemaFile); - JsonSchema schema = factory.getSchema(is); - JsonNode node = mapper.readTree(configuration); - Set errors = schema.validate(node); - if(errors.size()>0){ - LOGGER.error("Found {} errors in configuration file.", errors.size()); - } - for(ValidationMessage message : errors){ - LOGGER.error(message.getMessage()); - } - return errors.size()==0; - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/config/elements/ConnectionConfig.java b/src/main/java/org/aksw/iguana/cc/config/elements/ConnectionConfig.java index b53d5f71b..99addab0c 100644 --- a/src/main/java/org/aksw/iguana/cc/config/elements/ConnectionConfig.java +++ b/src/main/java/org/aksw/iguana/cc/config/elements/ConnectionConfig.java @@ -1,78 +1,38 @@ package org.aksw.iguana.cc.config.elements; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.io.Serializable; +import java.io.IOException; +import java.net.URI; /** * A connection configuration class */ -public class ConnectionConfig implements Serializable { - - @JsonProperty(required = true) - private String name; - @JsonProperty(required = false) - private String user; - @JsonProperty(required = false) - private String password; - @JsonProperty(required = true) - private String endpoint; - @JsonProperty(required = false) - private String updateEndpoint; - @JsonProperty(required = false) - private String version; - - public String getVersion() { - return version; - } - - public String getVersion(String defaultValue) { - if(version!=null) - return version; - return defaultValue; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getEndpoint() { - return endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public String getUpdateEndpoint() { - return updateEndpoint; - } - - public void setUpdateEndpoint(String updateEndpoint) { - this.updateEndpoint = updateEndpoint; - } +public record ConnectionConfig( + @JsonProperty(required = true) + String name, + String version, + DatasetConfig dataset, + @JsonProperty(required = true) + @JsonDeserialize(using = URIDeserializer.class) + URI endpoint, + Authentication authentication, + @JsonDeserialize(using = URIDeserializer.class) + URI updateEndpoint, + Authentication updateAuthentication + +) { + public static class URIDeserializer extends JsonDeserializer { + + @Override + public URI deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return URI.create(p.getValueAsString()); // verifying uri doesn't work here + } + } + + public record Authentication(String user, String password) {} } diff --git a/src/main/java/org/aksw/iguana/cc/config/elements/DatasetConfig.java b/src/main/java/org/aksw/iguana/cc/config/elements/DatasetConfig.java index d067b7158..8986de3be 100644 --- a/src/main/java/org/aksw/iguana/cc/config/elements/DatasetConfig.java +++ b/src/main/java/org/aksw/iguana/cc/config/elements/DatasetConfig.java @@ -4,29 +4,10 @@ /** * The Dataset config class. - * + *

* Will set the name and if it was set in the config file the fileName */ -public class DatasetConfig { - @JsonProperty(required = true) - private String name; - - @JsonProperty - private String file; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFile() { - return file; - } - - public void setFile(String file) { - this.file = file; - } -} +public record DatasetConfig( + @JsonProperty(required = true) String name, + @JsonProperty String file +) {} diff --git a/src/main/java/org/aksw/iguana/cc/config/elements/MetricConfig.java b/src/main/java/org/aksw/iguana/cc/config/elements/MetricConfig.java deleted file mode 100644 index 7f5ba8521..000000000 --- a/src/main/java/org/aksw/iguana/cc/config/elements/MetricConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.aksw.iguana.cc.config.elements; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.commons.factory.TypedFactory; - -import java.util.HashMap; -import java.util.Map; - -/** - * Metric Config class - */ -public class MetricConfig { - - @JsonProperty(required = true) - private String className; - - @JsonProperty() - private Map configuration = new HashMap<>(); - - - public String getClassName() { - return className; - } - - public void setClassName(String className) { - this.className = className; - } - - public Map getConfiguration() { - return configuration; - } - - public void setConfiguration(Map configuration) { - this.configuration = configuration; - } - - public Metric createMetric() { - TypedFactory factory = new TypedFactory<>(); - return factory.create(className, configuration); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/config/elements/StorageConfig.java b/src/main/java/org/aksw/iguana/cc/config/elements/StorageConfig.java index 5fe6127e8..bd55cace2 100644 --- a/src/main/java/org/aksw/iguana/cc/config/elements/StorageConfig.java +++ b/src/main/java/org/aksw/iguana/cc/config/elements/StorageConfig.java @@ -1,42 +1,24 @@ package org.aksw.iguana.cc.config.elements; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.aksw.iguana.commons.factory.TypedFactory; -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; - -import java.util.HashMap; -import java.util.Map; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.aksw.iguana.cc.storage.impl.CSVStorage; +import org.aksw.iguana.cc.storage.impl.RDFFileStorage; +import org.aksw.iguana.cc.storage.impl.TriplestoreStorage; /** * Storage Configuration class */ -public class StorageConfig { - - - @JsonProperty(required = true) - private String className; - - @JsonProperty - private Map configuration = new HashMap<>(); - - public String getClassName() { - return className; - } - public void setClassName(String className) { - this.className = className; - } +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = TriplestoreStorage.Config.class, name = "triplestore"), + @JsonSubTypes.Type(value = RDFFileStorage.Config.class, name = "rdf file"), + @JsonSubTypes.Type(value = CSVStorage.Config.class, name = "csv file") +}) +public interface StorageConfig {} - public Map getConfiguration() { - return configuration; - } - public void setConfiguration(Map configuration) { - this.configuration = configuration; - } - public Storage createStorage() { - TypedFactory factory = new TypedFactory<>(); - return factory.create(className, configuration); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/config/elements/TaskConfig.java b/src/main/java/org/aksw/iguana/cc/config/elements/TaskConfig.java deleted file mode 100644 index 5b09b3e45..000000000 --- a/src/main/java/org/aksw/iguana/cc/config/elements/TaskConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.aksw.iguana.cc.config.elements; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -/** - * The task configuration class, sets the class name and it's configuration - */ -public class TaskConfig implements Serializable { - - @JsonProperty(required = true) - private Map configuration = new HashMap<>(); - - @JsonProperty(required = true) - private String className; - - @JsonProperty() - private String name=null; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getConfiguration() { - return configuration; - } - - public String getClassName() { - return className; - } - - public void setClassName(String className) { - this.className = className; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/controller/MainController.java b/src/main/java/org/aksw/iguana/cc/controller/MainController.java index 9dd1b63ca..e9a03e70e 100644 --- a/src/main/java/org/aksw/iguana/cc/controller/MainController.java +++ b/src/main/java/org/aksw/iguana/cc/controller/MainController.java @@ -1,68 +1,73 @@ package org.aksw.iguana.cc.controller; -import org.aksw.iguana.cc.config.ConfigManager; +import com.beust.jcommander.*; +import org.aksw.iguana.cc.suite.IguanaSuiteParser; +import org.aksw.iguana.cc.suite.Suite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; + /** - * The main controller for the core. - * Will execute the Config Manager and the consuming for configurations. - * - * @author f.conrads - * + * The MainController class is responsible for executing the IGUANA program. */ public class MainController { - - private static final Logger LOGGER = LoggerFactory - .getLogger(MainController.class); - - /** - * main method for standalone controlling. - * If the TaskController should run standalone instead of in the core itself - * - * @param argc - * @throws IOException - */ - public static void main(String[] argc) throws IOException{ - if(argc.length != 1 && argc.length !=2){ - System.out.println("java -jar iguana.jar [--ignore-schema] suite.yml \n\tsuite.yml - The suite containing the benchmark configuration\n\t--ignore-schema - Will not validate configuration using the internal json schema\n"); - return; - } - MainController controller = new MainController(); - String config =argc[0]; - Boolean validate = true; - if(argc.length==2){ - if(argc[0].equals("--ignore-schema")){ - validate=false; - } - config = argc[1]; - } - controller.start(config, validate); - LOGGER.info("Stopping Iguana"); - //System.exit(0); - } + public static class Args { + public class PathConverter implements IStringConverter { + @Override + public Path convert(String value) { + return Path.of(value); + } + } + + + @Parameter(names = {"--ignore-schema", "-is"}, description = "Do not check the schema before parsing the suite file.") + private boolean ignoreShema = false; + + @Parameter(names = "--help", help = true) + private boolean help; + + @Parameter(description = "suite file {yml,yaml,json}", arity = 1, required = true, converter = PathConverter.class) + private Path suitePath; + } + + + private static final Logger LOGGER = LoggerFactory.getLogger(MainController.class); - /** - * Starts a configuration using the config file an states if Iguana should validate it using a json-schema - * - * @param configFile the Iguana config file - * @param validate should the config file be validated using a json-schema - * @throws IOException - */ - public void start(String configFile, Boolean validate) throws IOException{ - ConfigManager cmanager = new ConfigManager(); - File f = new File(configFile); - if (f.length()!=0) { - cmanager.receiveData(f, validate); - } else { - LOGGER.error("Empty configuration."); + /** + * The main method for executing IGUANA + * + * @param argc The command line arguments that are passed to the program. + */ + public static void main(String[] argc) { + var args = new Args(); + JCommander jc = JCommander.newBuilder() + .addObject(args) + .build(); + try { + jc.parse(argc); + } catch (ParameterException e) { + System.err.println(e.getLocalizedMessage()); + jc.usage(); + System.exit(0); + } + if (args.help) { + jc.usage(); + System.exit(1); + } - } + try { + Suite parse = IguanaSuiteParser.parse(args.suitePath, !args.ignoreShema); + parse.run(); + } catch (IOException e) { + LOGGER.error("Error while reading the configuration file.", e); + System.exit(0); + } + System.exit(0); + } - } } diff --git a/src/main/java/org/aksw/iguana/cc/controller/TaskController.java b/src/main/java/org/aksw/iguana/cc/controller/TaskController.java deleted file mode 100644 index 26f9652b7..000000000 --- a/src/main/java/org/aksw/iguana/cc/controller/TaskController.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.aksw.iguana.cc.controller; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.config.elements.TaskConfig; -import org.aksw.iguana.cc.tasks.TaskFactory; -import org.aksw.iguana.cc.tasks.TaskManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; - - -/** - * Task Controlling, will start the actual benchmark tasks and its {@link org.aksw.iguana.cc.tasks.TaskManager} - * - * @author f.conrads - */ -public class TaskController { - - private static final Logger LOGGER = LoggerFactory.getLogger(TaskController.class); - - public void startTask(String[] ids, String dataset, ConnectionConfig con, TaskConfig task) { - TaskManager tmanager = new TaskManager(); - String className = task.getClassName(); - TaskFactory factory = new TaskFactory(); - tmanager.setTask(factory.create(className, task.getConfiguration())); - try { - tmanager.startTask(ids, dataset, con, task.getName()); - } catch (IOException | TimeoutException e) { - LOGGER.error("Could not start Task " + className, e); - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/lang/AbstractLanguageProcessor.java b/src/main/java/org/aksw/iguana/cc/lang/AbstractLanguageProcessor.java deleted file mode 100644 index 10cce5b06..000000000 --- a/src/main/java/org/aksw/iguana/cc/lang/AbstractLanguageProcessor.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.aksw.iguana.cc.lang; - -import org.aksw.iguana.commons.constants.COMMON; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.aksw.iguana.commons.streams.Streams; -import org.apache.http.Header; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; -import org.json.simple.parser.ParseException; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.time.Instant; -import java.util.List; -import java.util.concurrent.TimeoutException; - -public abstract class AbstractLanguageProcessor implements LanguageProcessor { - - @Override - public String getQueryPrefix() { - return "query"; - } - - @Override - public Model generateTripleStats(List queries, String resourcePrefix, String taskID) { - Model model = ModelFactory.createDefaultModel(); - for(QueryWrapper wrappedQuery : queries) { - Resource subject = ResourceFactory.createResource(COMMON.RES_BASE_URI + resourcePrefix + "/" + wrappedQuery.getId()); - model.add(subject, RDF.type, IONT.query); - // TODO: fix this - model.add(subject, IPROP.queryID, ResourceFactory.createTypedLiteral(wrappedQuery.getId())); - model.add(subject, RDFS.label, wrappedQuery.getQuery().toString()); - } - return model; - } - - @Override - public Long getResultSize(CloseableHttpResponse response) throws ParserConfigurationException, SAXException, ParseException, IOException { - return response.getEntity().getContentLength(); - } - - @Override - public Long getResultSize(Header contentTypeHeader, ByteArrayOutputStream content, long contentLength) throws ParserConfigurationException, SAXException, ParseException, IOException { - return Long.valueOf(content.size()); - } - - @Override - public long readResponse(InputStream inputStream, ByteArrayOutputStream responseBody) throws IOException, TimeoutException { - return Streams.inputStream2ByteArrayOutputStream(inputStream, responseBody); - } - - //@Override - public long readResponse(InputStream inputStream, Instant startTime, Double timeOut, ByteArrayOutputStream responseBody) throws IOException, TimeoutException { - return Streams.inputStream2ByteArrayOutputStream(inputStream, startTime, timeOut, responseBody); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/lang/LanguageProcessor.java b/src/main/java/org/aksw/iguana/cc/lang/LanguageProcessor.java index 211d50aa8..bd902dd82 100644 --- a/src/main/java/org/aksw/iguana/cc/lang/LanguageProcessor.java +++ b/src/main/java/org/aksw/iguana/cc/lang/LanguageProcessor.java @@ -1,55 +1,69 @@ package org.aksw.iguana.cc.lang; -import org.apache.http.Header; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.jena.rdf.model.Model; -import org.json.simple.parser.ParseException; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +import org.aksw.iguana.cc.storage.Storable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.util.AnnotatedTypeScanner; + import java.io.InputStream; -import java.time.Instant; -import java.util.List; -import java.util.concurrent.TimeoutException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + /** - * Language Processor tells how to handle Http responses as well as how to analyze queries and generate stats. + * Interface for abstract language processors that work on InputStreams. */ -public interface LanguageProcessor { +public abstract class LanguageProcessor { /** - * Returns the prefix used for the queries (e.g. sparql, query or document) - * @return + * Provides the content type that a LanguageProcessor consumes. */ - String getQueryPrefix(); + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface ContentType { + String value(); + } - /** - * Method to generate Triple Statistics for provided queries - * - * - * @param taskID - * @return Model with the triples to add to the results - */ - Model generateTripleStats(List queries, String resourcePrefix, String taskID); + public interface LanguageProcessingData extends Storable { + long hash(); + Class processor(); + } + public abstract LanguageProcessingData process(InputStream inputStream, long hash); - /** - * Gets the result size of a given HTTP response - * - * @param response - * @return - * @throws ParserConfigurationException - * @throws SAXException - * @throws ParseException - * @throws IOException - */ - Long getResultSize(CloseableHttpResponse response) throws ParserConfigurationException, SAXException, ParseException, IOException; + final private static Map> processors = new HashMap<>(); - Long getResultSize(Header contentTypeHeader, ByteArrayOutputStream content, long contentLength) throws ParserConfigurationException, SAXException, ParseException, IOException; + final private static Logger LOGGER = LoggerFactory.getLogger(LanguageProcessor.class); + static { + final var scanner = new AnnotatedTypeScanner(false, ContentType.class); + final var langProcessors = scanner.findTypes("org.aksw.iguana.cc.lang"); + for (Class langProcessor : langProcessors) { + String contentType = langProcessor.getAnnotation(ContentType.class).value(); + if (LanguageProcessor.class.isAssignableFrom(langProcessor)) { + processors.put(contentType, (Class) langProcessor); + } else { + LOGGER.error("Found a class with the ContentType annotation, that doesn't inherit from the class LanguageProcessor: {}", langProcessor.getName()); + } + } + } - long readResponse(InputStream inputStream, ByteArrayOutputStream responseBody) throws IOException, TimeoutException; + public static LanguageProcessor getInstance(String contentType) { + Class processorClass = processors.get(contentType); + if (processorClass != null) { + try { + return processorClass.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + throw new IllegalArgumentException("No LanguageProcessor for ContentType " + contentType); + } } diff --git a/src/main/java/org/aksw/iguana/cc/lang/QueryWrapper.java b/src/main/java/org/aksw/iguana/cc/lang/QueryWrapper.java deleted file mode 100644 index 07ca5fb73..000000000 --- a/src/main/java/org/aksw/iguana/cc/lang/QueryWrapper.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.aksw.iguana.cc.lang; - -import java.math.BigInteger; - -/** - * Util class to wrap a Query of what ever class it may be and it's id - */ -public class QueryWrapper { - private final Object query; - private final int id; - private final String fullId; - - public QueryWrapper(Object query, String fullId) { - this.query = query; - int i = fullId.length(); - while (i > 0 && Character.isDigit(fullId.charAt(i - 1))) { - i--; - } - - this.id = Integer.parseInt(fullId.substring(i)); - this.fullId = fullId; - } - - public QueryWrapper(Object query, String prefix, int id) { - this.query = query; - this.id = id; - this.fullId = prefix + id; - } - - public Object getQuery() { - return query; - } - - public BigInteger getId() { - return BigInteger.valueOf(id); - } - - public String getFullId() { - return fullId; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/lang/impl/RDFLanguageProcessor.java b/src/main/java/org/aksw/iguana/cc/lang/impl/RDFLanguageProcessor.java deleted file mode 100644 index 80e68e1dc..000000000 --- a/src/main/java/org/aksw/iguana/cc/lang/impl/RDFLanguageProcessor.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.aksw.iguana.cc.lang.impl; - -import org.aksw.iguana.cc.lang.AbstractLanguageProcessor; -import org.aksw.iguana.cc.lang.LanguageProcessor; -import org.aksw.iguana.cc.lang.QueryWrapper; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.apache.http.Header; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.riot.Lang; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; -import org.json.simple.parser.ParseException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.util.List; - -/** - * Language for everything which returns RDF in any rdf format. - * - * Counts triples returned as ResultSize - */ -@Shorthand("lang.RDF") -public class RDFLanguageProcessor extends AbstractLanguageProcessor implements LanguageProcessor { - - private static Logger LOGGER = LoggerFactory.getLogger(RDFLanguageProcessor.class); - protected String queryPrefix="document"; - - @Override - public String getQueryPrefix() { - return this.queryPrefix; - } - - @Override - public Model generateTripleStats(List queries, String resourcePrefix, String taskID) { - Model model = ModelFactory.createDefaultModel(); - for(QueryWrapper wrappedQuery : queries) { - Resource subject = ResourceFactory.createResource(COMMON.RES_BASE_URI + resourcePrefix + "/" + wrappedQuery.getId()); - model.add(subject, RDF.type, IONT.query); - // TODO: fix this - model.add(subject, IPROP.queryID, ResourceFactory.createTypedLiteral(wrappedQuery.getId())); - model.add(subject, RDFS.label, wrappedQuery.getQuery().toString()); - } - return model; - } - - @Override - public Long getResultSize(CloseableHttpResponse response) throws ParserConfigurationException, SAXException, ParseException, IOException { - Model m; - try { - Header contentTypeHeader = response.getEntity().getContentType(); - InputStream inputStream = response.getEntity().getContent(); - m = getModel(contentTypeHeader, inputStream); - } catch (IllegalAccessException e) { - LOGGER.error("Could not read response as model", e); - return -1L; - } - return countSize(m); - } - - @Override - public Long getResultSize(Header contentTypeHeader, ByteArrayOutputStream content, long contentLength) throws IOException { - Model m; - try { - //TODO BBAIS - InputStream inputStream = new ByteArrayInputStream(content.toByteArray()); - m = getModel(contentTypeHeader, inputStream); - } catch (IllegalAccessException e) { - LOGGER.error("Could not read response as model", e); - return -1L; - } - return countSize(m); - } - - protected Long countSize(Model m) { - return m.size(); - } - - private Model getModel(Header contentTypeHeader, InputStream contentInputStream) throws IOException, IllegalAccessException { - Model m = ModelFactory.createDefaultModel(); - Lang lang = null; - // get actual content type - String contentType = contentTypeHeader.getValue(); - // use reflect to iterate over all static Lang fields of the Lang.class - for (Field langField : Lang.class.getFields()) { - //create the Language of the field - Lang susLang = (Lang) langField.get(Lang.class); - //if they are the same we have our language - if (contentType.equals(susLang.getContentType().getContentTypeStr())) { - lang = susLang; - break; - } - } - if (lang != null) - m.read(contentInputStream, null, lang.getName()); - return m; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/lang/impl/SPARQLLanguageProcessor.java b/src/main/java/org/aksw/iguana/cc/lang/impl/SPARQLLanguageProcessor.java deleted file mode 100644 index 5711f87d2..000000000 --- a/src/main/java/org/aksw/iguana/cc/lang/impl/SPARQLLanguageProcessor.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.aksw.iguana.cc.lang.impl; - -import org.aksw.iguana.cc.lang.AbstractLanguageProcessor; -import org.aksw.iguana.cc.lang.LanguageProcessor; -import org.aksw.iguana.cc.lang.QueryWrapper; -import org.aksw.iguana.cc.utils.SPARQLQueryStatistics; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.HttpEntity; -import org.apache.http.NameValuePair; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.jena.ext.com.google.common.hash.HashCode; -import org.apache.jena.ext.com.google.common.hash.Hashing; -import org.apache.jena.ext.com.google.common.io.BaseEncoding; -import org.apache.jena.query.Query; -import org.apache.jena.query.QueryFactory; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.vocabulary.OWL; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.*; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.aksw.iguana.commons.streams.Streams.inputStream2String; - -/** - * SPARQL Language Processor. - * Tries to analyze Queries as SPARQL queries and checks http response for either application/sparql-results+json - * or application/sparql-results+xml to count the result size correctly. Otherwise assumes it record per line and counts the returning lines. - */ -@Shorthand("lang.SPARQL") -public class SPARQLLanguageProcessor extends AbstractLanguageProcessor implements LanguageProcessor { - - private static Logger LOGGER = LoggerFactory.getLogger(SPARQLLanguageProcessor.class); - - public static final String XML_RESULT_ELEMENT_NAME = "result"; - public static final String XML_RESULT_ROOT_ELEMENT_NAME = "results"; - public static final String QUERY_RESULT_TYPE_JSON = "application/sparql-results+json"; - public static final String QUERY_RESULT_TYPE_XML = "application/sparql-results+xml"; - private static final String LSQ_RES = "http://lsq.aksw.org/res/q-"; - - @Override - public String getQueryPrefix() { - return "sparql"; - } - - @Override - public Model generateTripleStats(List queries, String resourcePrefix, String taskID) { - Model model = ModelFactory.createDefaultModel(); - for(QueryWrapper wrappedQuery : queries) { - Resource subject = ResourceFactory.createResource(COMMON.RES_BASE_URI + resourcePrefix + "/" + wrappedQuery.getId()); - model.add(subject, RDF.type, IONT.query); - // TODO: queryID is already used in a different context - model.add(subject, IPROP.queryID, ResourceFactory.createTypedLiteral(wrappedQuery.getId())); - model.add(subject, RDFS.label, wrappedQuery.getQuery().toString()); - try { - Query q = QueryFactory.create(wrappedQuery.getQuery().toString()); - SPARQLQueryStatistics qs2 = new SPARQLQueryStatistics(); - qs2.getStatistics(q); - - model.add(subject, IPROP.aggregations, model.createTypedLiteral(qs2.aggr==1)); - model.add(subject, IPROP.filter, model.createTypedLiteral(qs2.filter==1)); - model.add(subject, IPROP.groupBy, model.createTypedLiteral(qs2.groupBy==1)); - model.add(subject, IPROP.having, model.createTypedLiteral(qs2.having==1)); - model.add(subject, IPROP.triples, model.createTypedLiteral(qs2.triples)); - model.add(subject, IPROP.offset, model.createTypedLiteral(qs2.offset==1)); - model.add(subject, IPROP.optional, model.createTypedLiteral(qs2.optional==1)); - model.add(subject, IPROP.orderBy, model.createTypedLiteral(qs2.orderBy==1)); - model.add(subject, IPROP.union, model.createTypedLiteral(qs2.union==1)); - model.add(subject, OWL.sameAs, getLSQHash(q)); - }catch(Exception e){ - LOGGER.warn("Query statistics could not be created. Not using SPARQL?"); - } - } - return model; - } - - private Resource getLSQHash(Query query){ - HashCode hashCode = Hashing.sha256().hashString(query.toString(), StandardCharsets.UTF_8); - String result = BaseEncoding.base64Url().omitPadding().encode(hashCode.asBytes()); - return ResourceFactory.createResource(LSQ_RES+result); - } - - - public static String getContentTypeVal(Header header) { - for (HeaderElement el : header.getElements()) { - NameValuePair cTypePair = el.getParameterByName("Content-Type"); - - if (cTypePair != null && !cTypePair.getValue().isEmpty()) { - return cTypePair.getValue(); - } - } - int index = header.toString().indexOf("Content-Type"); - if (index >= 0) { - String ret = header.toString().substring(index + "Content-Type".length() + 1); - if (ret.contains(";")) { - return ret.substring(0, ret.indexOf(";")).trim(); - } - return ret.trim(); - } - return "application/sparql-results+json"; - } - - public static long getJsonResultSize(ByteArrayOutputStream res) throws ParseException, UnsupportedEncodingException { - JSONParser parser = new JSONParser(); - SaxSparqlJsonResultCountingParser handler = new SaxSparqlJsonResultCountingParser(); - parser.parse(res.toString(StandardCharsets.UTF_8), handler, true); - return handler.getNoBindings(); - } - - public static long getXmlResultSize(ByteArrayOutputStream res) throws ParserConfigurationException, IOException, SAXException { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - - ByteArrayInputStream bbais = new ByteArrayInputStream(res.toByteArray()); - Document doc = dBuilder.parse(bbais); - NodeList childNodes = doc.getDocumentElement().getElementsByTagName(XML_RESULT_ROOT_ELEMENT_NAME).item(0).getChildNodes(); - - long size = 0; - for (int i = 0; i < childNodes.getLength(); i++) { - if (XML_RESULT_ELEMENT_NAME.equalsIgnoreCase(childNodes.item(i).getNodeName())) { - size++; - } - } - return size; - - } - - @Override - public Long getResultSize(CloseableHttpResponse response) throws ParserConfigurationException, SAXException, ParseException, IOException { - HttpEntity httpResponse = response.getEntity(); - Header contentTypeHeader = response.getEntity().getContentType(); - - ByteArrayOutputStream entity; - try (InputStream inputStream = httpResponse.getContent()) { - - entity = inputStream2String(inputStream); - } catch (IOException e) { - LOGGER.error("Query result could not be read.", e); - throw e; - } - return getResultSize(contentTypeHeader, entity, entity.size()); - } - - @Override - public Long getResultSize(Header contentTypeHeader, ByteArrayOutputStream content, long contentLength) throws ParserConfigurationException, SAXException, ParseException, IOException { - try { - switch (getContentTypeVal(contentTypeHeader)) { - case QUERY_RESULT_TYPE_JSON: - return getJsonResultSize(content); - - case QUERY_RESULT_TYPE_XML: - return getXmlResultSize(content); - default: - //return content.countMatches('\n')+1; - long matches=0; - for(byte b: content.toByteArray()){ - if(b=='\n'){ - matches++; - } - } - return matches+1; - } - } catch (ParseException | ParserConfigurationException | IOException | SAXException e) { - LOGGER.error("Query results could not be parsed: ", e); - throw e; - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/lang/impl/SaxSparqlJsonResultCountingParser.java b/src/main/java/org/aksw/iguana/cc/lang/impl/SaxSparqlJsonResultCountingParser.java index d4c1f3e29..42a8a4eaf 100644 --- a/src/main/java/org/aksw/iguana/cc/lang/impl/SaxSparqlJsonResultCountingParser.java +++ b/src/main/java/org/aksw/iguana/cc/lang/impl/SaxSparqlJsonResultCountingParser.java @@ -1,114 +1,223 @@ package org.aksw.iguana.cc.lang.impl; +import org.aksw.iguana.cc.lang.LanguageProcessor; +import org.aksw.iguana.cc.storage.Storable; +import org.aksw.iguana.commons.rdf.IPROP; +import org.aksw.iguana.commons.rdf.IRES; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; import org.json.simple.parser.ContentHandler; +import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import static org.json.simple.parser.ParseException.ERROR_UNEXPECTED_EXCEPTION; /** * SAX Parser for SPARQL JSON Results. - * For correct SPARQL JSON Results it returns the correct size. + * For correct SPARQL JSON Results it returns the number of solutions, bound values and the names of the variables. * For malformed results it may or may not fail. For malformed JSON it fails if the underlying json.simple.parser fails. */ -class SaxSparqlJsonResultCountingParser implements ContentHandler { +@LanguageProcessor.ContentType("application/sparql-results+json") +public class SaxSparqlJsonResultCountingParser extends LanguageProcessor { - private boolean headFound = false; + @Override + public LanguageProcessingData process(InputStream inputStream, long hash) { + var parser = new JSONParser(); + var handler = new SaxSparqlJsonResultContentHandler(); + try { + parser.parse(new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)), handler); + return new SaxSparqlJsonResultData(hash, handler.solutions(), handler.boundValues(), handler.variables(), null); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (ParseException e) { + return new SaxSparqlJsonResultData(hash, -1, -1, null, e); + } + } - private int objectDepth = 0; - private boolean inResults = false; - private boolean inBindings = false; - private boolean inBindingsArray = false; + record SaxSparqlJsonResultData( + long hash, + long results, + long bindings, + List variables, + Exception exception + ) implements LanguageProcessingData, Storable.AsCSV, Storable.AsRDF { + final static String[] header = new String[]{ "responseBodyHash", "results", "bindings", "variables", "exception" }; + + @Override + public Class processor() { + return SaxSparqlJsonResultCountingParser.class; + } - private long noBindings = 0; + @Override + public CSVData toCSV() { + String variablesString = ""; + String exceptionString = ""; + if (variables != null) + variablesString = String.join("; ", variables); + if (exception != null) + exceptionString = exception().toString(); + + String[] content = new String[]{ String.valueOf(hash), String.valueOf(results), String.valueOf(bindings), variablesString, exceptionString}; + String[][] data = new String[][]{ header, content }; + + String folderName = "application-sparql+json"; + List files = List.of(new CSVData.CSVFileData("sax-sparql-result-data.csv", data)); + return new Storable.CSVData(folderName, files); + } - public long getNoBindings() { - return noBindings; - } + @Override + public Model toRDF() { + Model m = ModelFactory.createDefaultModel(); + Resource responseBodyRes = IRES.getResponsebodyResource(this.hash); + m.add(responseBodyRes, IPROP.results, ResourceFactory.createTypedLiteral(this.results)) + .add(responseBodyRes, IPROP.bindings, ResourceFactory.createTypedLiteral(this.bindings)); - @Override - public void startJSON() { - } + if (this.variables != null) { + for (String variable : this.variables) { + m.add(responseBodyRes, IPROP.variable, ResourceFactory.createTypedLiteral(variable)); + } + } + if (this.exception != null) { + m.add(responseBodyRes, IPROP.exception, ResourceFactory.createTypedLiteral(this.exception.toString())); + } - @Override - public void endJSON() throws ParseException { - if (inResults || inBindings || inBindingsArray || !headFound || objectDepth != 0) - throw new ParseException(ERROR_UNEXPECTED_EXCEPTION, "SPARQL Json Response was malformed."); + return m; + } } - @Override - public boolean startObject() { - objectDepth += 1; - if (objectDepth == 3 && inBindingsArray) { - noBindings += 1; + private static class SaxSparqlJsonResultContentHandler implements ContentHandler { + // TODO: add support for ask queries and link + // TODO: code is unnecessary complicated + + private boolean headFound = false; + + private int objectDepth = 0; + private boolean inResults = false; + private boolean inBindings = false; + private boolean inBindingsArray = false; + private boolean inVars = false; + + private long boundValues = 0; + + private long solutions = 0; + + private final List variables = new ArrayList<>(); + + + @Override + public void startJSON() { } - return true; - } - @Override - public boolean endObject() { - switch (objectDepth) { - case 1: - if (inResults) - inResults = false; - break; - case 2: - if (inBindings) { - inBindings = false; + @Override + public void endJSON() throws ParseException { + if (inResults || inBindings || inBindingsArray || !headFound || objectDepth != 0) + throw new ParseException(ERROR_UNEXPECTED_EXCEPTION, "SPARQL Json Response was malformed."); + } + + @Override + public boolean startObject() { + objectDepth += 1; + if (inBindingsArray) { + switch (objectDepth) { + case 3 -> solutions += 1; + case 4 -> boundValues += 1; } - break; + } + return true; } - objectDepth -= 1; - return true; - } - @Override - public boolean startArray() { - if (objectDepth == 2 && inResults && inBindings && !inBindingsArray) { - inBindingsArray = true; + @Override + public boolean endObject() { + switch (objectDepth) { + case 1: + if (inResults) + inResults = false; + break; + case 2: + if (inBindings) { + inBindings = false; + } + break; + } + objectDepth -= 1; + return true; } - return true; - } - @Override - public boolean endArray() { - if (objectDepth == 2 && inResults && inBindings && inBindingsArray) { - inBindingsArray = false; + @Override + public boolean startArray() { + if (objectDepth == 2 && inResults && inBindings && !inBindingsArray) { + inBindingsArray = true; + } + return true; } - return true; - } - @Override - public boolean startObjectEntry(String key) { - switch (objectDepth) { - case 1: - switch (key) { - case "head": - headFound = true; - break; - case "results": - if (headFound) - inResults = true; - break; + @Override + public boolean endArray() { + if (inVars) + inVars = false; + if (objectDepth == 2 && inResults && inBindings && inBindingsArray) { + inBindingsArray = false; + } + return true; + } + + + @Override + public boolean startObjectEntry(String key) { + switch (objectDepth) { + case 1 -> { + switch (key) { + case "head" -> headFound = true; + case "results" -> { + if (headFound) + inResults = true; + } + } } - break; - case 2: - if ("bindings".compareTo(key) == 0) { - inBindings = true; + case 2 -> { + if ("bindings".compareTo(key) == 0) { + inBindings = true; + } + if ("vars".compareTo(key) == 0) { + inVars = true; + } } - break; + } + return true; } - return true; - } - @Override - public boolean endObjectEntry() { - return true; - } + @Override + public boolean endObjectEntry() { + return true; + } - public boolean primitive(Object value) { - return true; - } + public boolean primitive(Object value) { + if (inVars) + variables.add(value.toString()); + return true; + } + public long boundValues() { + return boundValues; + } + + public long solutions() { + return solutions; + } + + public List variables() { + return variables; + } + } } \ No newline at end of file diff --git a/src/main/java/org/aksw/iguana/cc/lang/impl/ThrowawayLanguageProcessor.java b/src/main/java/org/aksw/iguana/cc/lang/impl/ThrowawayLanguageProcessor.java deleted file mode 100644 index 5f2267936..000000000 --- a/src/main/java/org/aksw/iguana/cc/lang/impl/ThrowawayLanguageProcessor.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.aksw.iguana.cc.lang.impl; - -import org.aksw.iguana.cc.lang.AbstractLanguageProcessor; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.streams.Streams; -import org.apache.http.Header; -import org.json.simple.parser.ParseException; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.time.Instant; -import java.util.concurrent.TimeoutException; - -@Shorthand("lang.SIMPLE") -public class ThrowawayLanguageProcessor extends AbstractLanguageProcessor { - - @Override - public long readResponse(InputStream inputStream, ByteArrayOutputStream responseBody) throws IOException, TimeoutException { - return Streams.inputStream2Length(inputStream, Instant.now(), 0); - } - - @Override - public long readResponse(InputStream inputStream, Instant startTime, Double timeOut, ByteArrayOutputStream responseBody) throws IOException, TimeoutException { - return Streams.inputStream2Length(inputStream, startTime, timeOut); - } - - @Override - public Long getResultSize(Header contentTypeHeader, ByteArrayOutputStream content, long contentLength) throws ParserConfigurationException, SAXException, ParseException, IOException { - return contentLength; - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/Metric.java b/src/main/java/org/aksw/iguana/cc/metrics/Metric.java new file mode 100644 index 000000000..0f4bc15fa --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/Metric.java @@ -0,0 +1,41 @@ +package org.aksw.iguana.cc.metrics; + +import com.fasterxml.jackson.annotation.*; +import org.aksw.iguana.cc.metrics.impl.*; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = AggregatedExecutionStatistics.class, name = "AES"), + @JsonSubTypes.Type(value = AvgQPS.class, name = "AvgQPS"), + @JsonSubTypes.Type(value = EachExecutionStatistic.class, name = "EachQuery"), + @JsonSubTypes.Type(value = NoQ.class, name = "NoQ"), + @JsonSubTypes.Type(value = NoQPH.class, name = "NoQPH"), + @JsonSubTypes.Type(value = PAvgQPS.class, name = "PAvgQPS"), + @JsonSubTypes.Type(value = PQPS.class, name = "PQPS"), + @JsonSubTypes.Type(value = QMPH.class, name = "QMPH"), + @JsonSubTypes.Type(value = QPS.class, name = "QPS") +}) +public abstract class Metric { + private final String name; + private final String abbreviation; + private final String description; + + public Metric(String name, String abbreviation, String description) { + this.name = name; + this.abbreviation = abbreviation; + this.description = description; + } + + + public String getDescription(){ + return this.description; + } + + public String getName(){ + return this.name; + } + + public String getAbbreviation(){ + return this.abbreviation; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/ModelWritingMetric.java b/src/main/java/org/aksw/iguana/cc/metrics/ModelWritingMetric.java new file mode 100644 index 000000000..9debe1481 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/ModelWritingMetric.java @@ -0,0 +1,19 @@ +package org.aksw.iguana.cc.metrics; + +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.commons.rdf.IRES; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; + +import java.util.List; +import java.util.Map; + +public interface ModelWritingMetric { + default Model createMetricModel(List workers, List[][] data, IRES.Factory iresFactory) { + return ModelFactory.createDefaultModel(); + } + + default Model createMetricModel(List workers, Map> data, IRES.Factory iresFactory) { + return ModelFactory.createDefaultModel(); + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/QueryMetric.java b/src/main/java/org/aksw/iguana/cc/metrics/QueryMetric.java new file mode 100644 index 000000000..9b771a570 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/QueryMetric.java @@ -0,0 +1,9 @@ +package org.aksw.iguana.cc.metrics; + +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.util.List; + +public interface QueryMetric { + Number calculateQueryMetric(List data); +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/TaskMetric.java b/src/main/java/org/aksw/iguana/cc/metrics/TaskMetric.java new file mode 100644 index 000000000..8b4360306 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/TaskMetric.java @@ -0,0 +1,9 @@ +package org.aksw.iguana.cc.metrics; + +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.util.List; + +public interface TaskMetric { + Number calculateTaskMetric(List workers, List[][] data); +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/WorkerMetric.java b/src/main/java/org/aksw/iguana/cc/metrics/WorkerMetric.java new file mode 100644 index 000000000..1fe5b763f --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/WorkerMetric.java @@ -0,0 +1,9 @@ +package org.aksw.iguana.cc.metrics; + +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.util.List; + +public interface WorkerMetric { + Number calculateWorkerMetric(HttpWorker.Config worker, List[] data); +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/AggregatedExecutionStatistics.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/AggregatedExecutionStatistics.java new file mode 100644 index 000000000..e0942dba9 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/AggregatedExecutionStatistics.java @@ -0,0 +1,88 @@ +package org.aksw.iguana.cc.metrics.impl; + +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.ModelWritingMetric; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.commons.rdf.IONT; +import org.aksw.iguana.commons.rdf.IPROP; +import org.aksw.iguana.commons.rdf.IRES; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; +import org.apache.jena.vocabulary.RDF; + +import java.math.BigInteger; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.aksw.iguana.commons.time.TimeUtils.toXSDDurationInSeconds; + +public class AggregatedExecutionStatistics extends Metric implements ModelWritingMetric { + + public AggregatedExecutionStatistics() { + super("Aggregated Execution Statistics", "AES", "Sums up the statistics of each query execution for each query a worker and task has. The result size only contains the value of the last execution."); + } + + @Override + public Model createMetricModel(List workers, List[][] data, IRES.Factory iresFactory) { + Model m = ModelFactory.createDefaultModel(); + for (var worker : workers) { + for (int i = 0; i < worker.config().queries().getQueryCount(); i++) { + Resource queryRes = iresFactory.getWorkerQueryResource(worker, i); + m.add(createAggregatedModel(data[(int) worker.getWorkerID()][i], queryRes)); + } + } + return m; + } + + @Override + public Model createMetricModel(List workers, Map> data, IRES.Factory iresFactory) { + Model m = ModelFactory.createDefaultModel(); + for (String queryID : data.keySet()) { + Resource queryRes = iresFactory.getTaskQueryResource(queryID); + m.add(createAggregatedModel(data.get(queryID), queryRes)); + } + return m; + } + + private static Model createAggregatedModel(List data, Resource queryRes) { + Model m = ModelFactory.createDefaultModel(); + BigInteger succeeded = BigInteger.ZERO; + BigInteger failed = BigInteger.ZERO; + Optional resultSize = Optional.empty(); + BigInteger wrongCodes = BigInteger.ZERO; + BigInteger timeOuts = BigInteger.ZERO; + BigInteger unknownExceptions = BigInteger.ZERO; + Duration totalTime = Duration.ZERO; + + for (HttpWorker.ExecutionStats exec : data) { + switch (exec.endState()) { + case SUCCESS -> succeeded = succeeded.add(BigInteger.ONE); + case TIMEOUT -> timeOuts = timeOuts.add(BigInteger.ONE); + case HTTP_ERROR -> wrongCodes = wrongCodes.add(BigInteger.ONE); + case MISCELLANEOUS_EXCEPTION -> unknownExceptions = unknownExceptions.add(BigInteger.ONE); + } + + if (!exec.successful()) + failed = failed.add(BigInteger.ONE); + + totalTime = totalTime.plus(exec.duration()); + if (exec.contentLength().isPresent()) + resultSize = Optional.of(BigInteger.valueOf(exec.contentLength().getAsLong())); + } + + m.add(queryRes, IPROP.succeeded, ResourceFactory.createTypedLiteral(succeeded)); + m.add(queryRes, IPROP.failed, ResourceFactory.createTypedLiteral(failed)); + m.add(queryRes, IPROP.resultSize, ResourceFactory.createTypedLiteral(resultSize.orElse(BigInteger.valueOf(-1)))); + m.add(queryRes, IPROP.timeOuts, ResourceFactory.createTypedLiteral(timeOuts)); + m.add(queryRes, IPROP.wrongCodes, ResourceFactory.createTypedLiteral(wrongCodes)); + m.add(queryRes, IPROP.unknownException, ResourceFactory.createTypedLiteral(unknownExceptions)); + m.add(queryRes, IPROP.totalTime, ResourceFactory.createTypedLiteral(toXSDDurationInSeconds(totalTime))); + m.add(queryRes, RDF.type, IONT.executedQuery); + + return m; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/AvgQPS.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/AvgQPS.java new file mode 100644 index 000000000..cb27e55b4 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/AvgQPS.java @@ -0,0 +1,45 @@ +package org.aksw.iguana.cc.metrics.impl; + +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.TaskMetric; +import org.aksw.iguana.cc.metrics.WorkerMetric; +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +public class AvgQPS extends Metric implements TaskMetric, WorkerMetric { + + public AvgQPS() { + super("Average Queries per Second", "AvgQPS", "This metric calculates the average QPS between all queries."); + } + + @Override + public Number calculateTaskMetric(List workers, List[][] data) { + final var sum = workers.stream() + .map(worker -> (BigDecimal) this.calculateWorkerMetric(worker.config(), data[(int) worker.getWorkerID()])) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + try { + return sum.divide(BigDecimal.valueOf(data.length), 10, RoundingMode.HALF_UP); + } catch (ArithmeticException e) { + return BigDecimal.ZERO; + } + } + + @Override + public Number calculateWorkerMetric(HttpWorker.Config worker, List[] data) { + BigDecimal sum = BigDecimal.ZERO; + QPS qpsmetric = new QPS(); + for (List datum : data) { + sum = sum.add((BigDecimal) qpsmetric.calculateQueryMetric(datum)); + } + + try { + return sum.divide(BigDecimal.valueOf(data.length), 10, RoundingMode.HALF_UP); + } catch (ArithmeticException e) { + return BigDecimal.ZERO; + } + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/EachExecutionStatistic.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/EachExecutionStatistic.java new file mode 100644 index 000000000..d8b267f56 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/EachExecutionStatistic.java @@ -0,0 +1,56 @@ +package org.aksw.iguana.cc.metrics.impl; + +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.ModelWritingMetric; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.commons.rdf.IPROP; +import org.aksw.iguana.commons.rdf.IRES; +import org.aksw.iguana.commons.time.TimeUtils; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; + +import java.math.BigInteger; +import java.util.List; + +public class EachExecutionStatistic extends Metric implements ModelWritingMetric { + + public EachExecutionStatistic() { + super("Each Query Execution Statistic", "EachQuery", "This metric saves the statistics of each query execution."); + } + + @Override + public Model createMetricModel(List workers, List[][] data, IRES.Factory iresFactory) { + Model m = ModelFactory.createDefaultModel(); + for (var worker : workers) { + for (int i = 0; i < worker.config().queries().getQueryCount(); i++) { + Resource workerQueryResource = iresFactory.getWorkerQueryResource(worker, i); + Resource queryRes = IRES.getResource(worker.config().queries().getQueryId(i)); + BigInteger run = BigInteger.ONE; + for (HttpWorker.ExecutionStats exec : data[(int) worker.getWorkerID()][i]) { + Resource runRes = iresFactory.getWorkerQueryRunResource(worker, i, run); + m.add(workerQueryResource, IPROP.queryExecution, runRes); + m.add(runRes, IPROP.time, TimeUtils.createTypedDurationLiteral(exec.duration())); + m.add(runRes, IPROP.startTime, TimeUtils.createTypedInstantLiteral(exec.startTime())); + m.add(runRes, IPROP.success, ResourceFactory.createTypedLiteral(exec.successful())); + m.add(runRes, IPROP.run, ResourceFactory.createTypedLiteral(run)); + m.add(runRes, IPROP.code, ResourceFactory.createTypedLiteral(exec.endState().value)); + m.add(runRes, IPROP.resultSize, ResourceFactory.createTypedLiteral(exec.contentLength().orElse(-1))); + m.add(runRes, IPROP.queryID, queryRes); + if (exec.responseBodyHash().isPresent()) { + Resource responseBodyRes = IRES.getResponsebodyResource(exec.responseBodyHash().getAsLong()); + m.add(runRes, IPROP.responseBody, responseBodyRes); + m.add(responseBodyRes, IPROP.responseBodyHash, ResourceFactory.createTypedLiteral(exec.responseBodyHash().getAsLong())); + } + if (exec.error().isPresent()) + m.add(runRes, IPROP.exception, ResourceFactory.createTypedLiteral(exec.error().get().toString())); + if (exec.httpStatusCode().isPresent()) + m.add(runRes, IPROP.httpCode, ResourceFactory.createTypedLiteral(exec.httpStatusCode().get().toString())); + run = run.add(BigInteger.ONE); + } + } + } + return m; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/NoQ.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/NoQ.java new file mode 100644 index 000000000..411f73ca9 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/NoQ.java @@ -0,0 +1,38 @@ +package org.aksw.iguana.cc.metrics.impl; + +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.TaskMetric; +import org.aksw.iguana.cc.metrics.WorkerMetric; +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +public class NoQ extends Metric implements TaskMetric, WorkerMetric { + + public NoQ() { + super("Number of Queries", "NoQ", "This metric calculates the number of successfully executed queries."); + } + + @Override + public Number calculateTaskMetric(List workers, List[][] data) { + final var sum = workers.stream() + .map(worker -> (BigInteger) this.calculateWorkerMetric(worker.config(), data[(int) worker.getWorkerID()])) + .reduce(BigInteger.ZERO, BigInteger::add); + return sum; + } + + @Override + public Number calculateWorkerMetric(HttpWorker.Config worker, List[] data) { + BigInteger sum = BigInteger.ZERO; + for (List datum : data) { + for (HttpWorker.ExecutionStats exec : datum) { + if (exec.successful()) { + sum = sum.add(BigInteger.ONE); + } + } + } + return sum; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/NoQPH.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/NoQPH.java new file mode 100644 index 000000000..790f17a89 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/NoQPH.java @@ -0,0 +1,47 @@ +package org.aksw.iguana.cc.metrics.impl; + +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.TaskMetric; +import org.aksw.iguana.cc.metrics.WorkerMetric; +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.time.Duration; +import java.util.List; + +public class NoQPH extends Metric implements TaskMetric, WorkerMetric { + + public NoQPH() { + super("Number of Queries per Hour", "NoQPH", "This metric calculates the number of successfully executed queries per hour."); + } + @Override + public Number calculateTaskMetric(List workers, List[][] data) { + final var sum = workers.stream() + .map(worker -> (BigDecimal) this.calculateWorkerMetric(worker.config(), data[(int) worker.getWorkerID()])) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return sum; + } + + @Override + public Number calculateWorkerMetric(HttpWorker.Config worker, List[] data) { + BigDecimal successes = BigDecimal.ZERO; + Duration totalTime = Duration.ZERO; + for (List datum : data) { + for (HttpWorker.ExecutionStats exec : datum) { + if (exec.successful()) { + successes = successes.add(BigDecimal.ONE); + totalTime = totalTime.plus(exec.duration()); + } + } + } + BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)).divide(BigDecimal.valueOf(3600), 20, RoundingMode.HALF_UP); + + try { + return successes.divide(tt, 10, RoundingMode.HALF_UP); + } catch (ArithmeticException e) { + return BigDecimal.ZERO; + } + } +} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/PAvgQPS.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/PAvgQPS.java similarity index 52% rename from src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/PAvgQPS.java rename to src/main/java/org/aksw/iguana/cc/metrics/impl/PAvgQPS.java index f71d42ae3..d22472a55 100644 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/PAvgQPS.java +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/PAvgQPS.java @@ -1,33 +1,29 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; +package org.aksw.iguana.cc.metrics.impl; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.TaskMetric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.WorkerMetric; -import org.aksw.iguana.commons.annotation.Shorthand; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.TaskMetric; +import org.aksw.iguana.cc.metrics.WorkerMetric; +import org.aksw.iguana.cc.worker.HttpWorker; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; -@Shorthand("PAvgQPS") public class PAvgQPS extends Metric implements TaskMetric, WorkerMetric { private final int penalty; - public PAvgQPS(Integer penalty) { + public PAvgQPS(@JsonProperty("penalty") Integer penalty) { super("Penalized Average Queries per Second", "PAvgQPS", "This metric calculates the average QPS between all queries. Failed executions receive a time penalty."); this.penalty = penalty; } @Override - public Number calculateTaskMetric(StresstestMetadata task, List[][] data) { - BigDecimal sum = BigDecimal.ZERO; - for (WorkerMetadata worker : task.workers()) { - sum = sum.add((BigDecimal) this.calculateWorkerMetric(worker, data[worker.workerID()])); - } + public Number calculateTaskMetric(List workers, List[][] data) { + final var sum = workers.stream() + .map(worker -> (BigDecimal) this.calculateWorkerMetric(worker.config(), data[(int) worker.getWorkerID()])) + .reduce(BigDecimal.ZERO, BigDecimal::add); try { return sum.divide(BigDecimal.valueOf(data.length), 10, RoundingMode.HALF_UP); @@ -37,10 +33,10 @@ public Number calculateTaskMetric(StresstestMetadata task, List[] data) { + public Number calculateWorkerMetric(HttpWorker.Config worker, List[] data) { BigDecimal sum = BigDecimal.ZERO; PQPS pqpsmetric = new PQPS(penalty); - for (List datum : data) { + for (List datum : data) { sum = sum.add((BigDecimal) pqpsmetric.calculateQueryMetric(datum)); } if (data.length == 0) { diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/PQPS.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/PQPS.java new file mode 100644 index 000000000..78b237c5e --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/PQPS.java @@ -0,0 +1,43 @@ +package org.aksw.iguana.cc.metrics.impl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.QueryMetric; +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.time.Duration; +import java.util.List; + +public class PQPS extends Metric implements QueryMetric { + + private final int penalty; + + public PQPS(@JsonProperty("penalty") Integer penalty) { + super("Penalized Queries per Second", "PQPS", "This metric calculates for each query the amount of executions per second. Failed executions receive a time penalty."); + this.penalty = penalty; + } + + @Override + public Number calculateQueryMetric(List data) { + BigDecimal numberOfExecutions = BigDecimal.ZERO; + Duration totalTime = Duration.ZERO; + for (HttpWorker.ExecutionStats exec : data) { + numberOfExecutions = numberOfExecutions.add(BigDecimal.ONE); + if (exec.successful()) { + totalTime = totalTime.plus(exec.duration()); + } else { + totalTime = totalTime.plusMillis(penalty); + } + } + BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)); + + try { + return numberOfExecutions.divide(tt, 10, RoundingMode.HALF_UP); + } catch (ArithmeticException e) { + return BigDecimal.ZERO; + } + } +} diff --git a/src/main/java/org/aksw/iguana/cc/metrics/impl/QMPH.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/QMPH.java new file mode 100644 index 000000000..d2ae19143 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/QMPH.java @@ -0,0 +1,49 @@ +package org.aksw.iguana.cc.metrics.impl; + +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.TaskMetric; +import org.aksw.iguana.cc.metrics.WorkerMetric; +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.time.Duration; +import java.util.List; + +public class QMPH extends Metric implements TaskMetric, WorkerMetric { + + public QMPH() { + super("Query Mixes per Hour", "QMPH", "This metric calculates the amount of query mixes (a given set of queries) that are executed per hour."); + } + + @Override + public Number calculateTaskMetric(List workers, List[][] data) { + final var sum = workers.stream() + .map(worker -> (BigDecimal) this.calculateWorkerMetric(worker.config(), data[(int) worker.getWorkerID()])) + .reduce(BigDecimal.ZERO, BigDecimal::add); + return sum; + } + + @Override + public Number calculateWorkerMetric(HttpWorker.Config worker, List[] data) { + BigDecimal successes = BigDecimal.ZERO; + BigDecimal noq = BigDecimal.valueOf(worker.queries().getQueryCount()); + Duration totalTime = Duration.ZERO; + for (List datum : data) { + for (HttpWorker.ExecutionStats exec : datum) { + if (exec.successful()) { + successes = successes.add(BigDecimal.ONE); + totalTime = totalTime.plus(exec.duration()); + } + } + } + BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)).divide(BigDecimal.valueOf(3600), 20, RoundingMode.HALF_UP); + + try { + return successes.divide(tt, 10, RoundingMode.HALF_UP).divide(noq, 10, RoundingMode.HALF_UP); + } catch (ArithmeticException e) { + return BigDecimal.ZERO; + } + } +} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/QPS.java b/src/main/java/org/aksw/iguana/cc/metrics/impl/QPS.java similarity index 56% rename from src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/QPS.java rename to src/main/java/org/aksw/iguana/cc/metrics/impl/QPS.java index 888c7bb49..b20e2d84d 100644 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/QPS.java +++ b/src/main/java/org/aksw/iguana/cc/metrics/impl/QPS.java @@ -1,10 +1,8 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; +package org.aksw.iguana.cc.metrics.impl; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.QueryMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.QueryMetric; +import org.aksw.iguana.cc.worker.HttpWorker; import java.math.BigDecimal; import java.math.BigInteger; @@ -12,7 +10,6 @@ import java.time.Duration; import java.util.List; -@Shorthand("QPS") public class QPS extends Metric implements QueryMetric { public QPS() { @@ -20,13 +17,13 @@ public QPS() { } @Override - public Number calculateQueryMetric(List data) { + public Number calculateQueryMetric(List data) { BigDecimal successes = BigDecimal.ZERO; Duration totalTime = Duration.ZERO; - for (QueryExecutionStats exec : data) { - if (exec.responseCode() == COMMON.QUERY_SUCCESS) { + for (HttpWorker.ExecutionStats exec : data) { + if (exec.successful()) { successes = successes.add(BigDecimal.ONE); - totalTime = totalTime.plusNanos((long) exec.executionTime() * 1000000); + totalTime = totalTime.plus(exec.duration()); } } BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)); diff --git a/src/main/java/org/aksw/iguana/cc/model/QueryExecutionStats.java b/src/main/java/org/aksw/iguana/cc/model/QueryExecutionStats.java deleted file mode 100644 index c15a6b478..000000000 --- a/src/main/java/org/aksw/iguana/cc/model/QueryExecutionStats.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.aksw.iguana.cc.model; - -/** - * Wrapper for a query execution. - */ -public record QueryExecutionStats ( - String queryID, - long responseCode, - double executionTime, - long resultSize -) { - public QueryExecutionStats(String queryID, long responseCode, double executionTime) { - this(queryID, responseCode, executionTime, 0); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/model/QueryResultHashKey.java b/src/main/java/org/aksw/iguana/cc/model/QueryResultHashKey.java deleted file mode 100644 index 21ad255c6..000000000 --- a/src/main/java/org/aksw/iguana/cc/model/QueryResultHashKey.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.aksw.iguana.cc.model; - -import java.util.Objects; - -/** - * Creates a Result Hash key for a query, thus a result size only has to be checked once and it will be cached using this key - */ -public class QueryResultHashKey { - - private String queryId; - private long uniqueKey; - - public QueryResultHashKey(String queryId, long uniqueKey) { - this.queryId = queryId; - this.uniqueKey = uniqueKey; - } - - public String getQueryId() { - return queryId; - } - - public void setQueryId(String queryId) { - this.queryId = queryId; - } - - public long getUniqueKey() { - return uniqueKey; - } - - public void setUniqueKey(long uniqueKey) { - this.uniqueKey = uniqueKey; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - QueryResultHashKey that = (QueryResultHashKey) o; - return uniqueKey == that.uniqueKey && - queryId.equals(that.queryId); - } - - @Override - public int hashCode() { - return Objects.hash(queryId, uniqueKey); - } - - @Override - public String toString() { - return "QueryResultHashKey{" + - "queryId='" + queryId + '\'' + - ", uniqueKey=" + uniqueKey + - '}'; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/query/handler/QueryHandler.java b/src/main/java/org/aksw/iguana/cc/query/handler/QueryHandler.java index dee5bfab1..1c9ac2eee 100644 --- a/src/main/java/org/aksw/iguana/cc/query/handler/QueryHandler.java +++ b/src/main/java/org/aksw/iguana/cc/query/handler/QueryHandler.java @@ -1,28 +1,28 @@ package org.aksw.iguana.cc.query.handler; -import org.aksw.iguana.cc.lang.LanguageProcessor; -import org.aksw.iguana.cc.lang.QueryWrapper; -import org.aksw.iguana.cc.query.pattern.PatternHandler; +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.aksw.iguana.cc.query.selector.QuerySelector; import org.aksw.iguana.cc.query.selector.impl.LinearQuerySelector; import org.aksw.iguana.cc.query.selector.impl.RandomQuerySelector; import org.aksw.iguana.cc.query.list.QueryList; import org.aksw.iguana.cc.query.list.impl.FileBasedQueryList; import org.aksw.iguana.cc.query.list.impl.InMemQueryList; -import org.aksw.iguana.cc.query.source.QuerySource; import org.aksw.iguana.cc.query.source.impl.FileLineQuerySource; import org.aksw.iguana.cc.query.source.impl.FileSeparatorQuerySource; import org.aksw.iguana.cc.query.source.impl.FolderQuerySource; -import org.aksw.iguana.commons.factory.TypedFactory; -import org.apache.jena.rdf.model.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.ArrayList; +import java.io.InputStream; +import java.nio.file.Path; import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Objects; /** * The QueryHandler is used by every worker that extends the AbstractWorker. @@ -32,185 +32,176 @@ * * @author frensing */ +@JsonDeserialize(using = QueryHandler.Deserializer.class) public class QueryHandler { + static class Deserializer extends StdDeserializer { + final HashMap queryHandlers = new HashMap<>(); + protected Deserializer(Class vc) { + super(vc); + } - protected final Logger LOGGER = LoggerFactory.getLogger(QueryHandler.class); + protected Deserializer() { + this(null); + } - protected Map config; - protected Integer workerID; - protected String location; - protected int hashcode; + @Override + public QueryHandler deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + QueryHandler.Config queryHandlerConfig = ctxt.readValue(jp, QueryHandler.Config.class); + if (!queryHandlers.containsKey(queryHandlerConfig)) + queryHandlers.put(queryHandlerConfig, new QueryHandler(queryHandlerConfig)); - protected boolean caching; + return queryHandlers.get(queryHandlerConfig); + } + } - protected QuerySelector querySelector; + public record Config ( + String path, + Format format, + String separator, + Boolean caching, + Order order, + Long seed, + Language lang + ) { + public Config(@JsonProperty(required = true) String path, Format format, String separator, Boolean caching, Order order, Long seed, Language lang) { + this.path = path; + this.format = (format == null ? Format.ONE_PER_LINE : format); + this.caching = (caching == null || caching); + this.order = (order == null ? Order.LINEAR : order); + this.seed = (seed == null ? 0 : seed); + this.lang = (lang == null ? Language.SPARQL : lang); + this.separator = (separator == null ? "" : separator); + } - protected QueryList queryList; + public enum Format { + @JsonEnumDefaultValue ONE_PER_LINE("one-per-line"), + SEPARATOR("separator"), + FOLDER("folder"); - protected LanguageProcessor langProcessor; + final String value; - public QueryHandler(Map config, Integer workerID) { - this.config = config; - this.workerID = workerID; + Format(String value) { + this.value = Objects.requireNonNullElse(value, "one-per-line"); + } - this.location = (String) config.get("location"); + @JsonValue + public String value() { + return value; + } + } - initQuerySet(); + public enum Order { + @JsonEnumDefaultValue LINEAR("linear"), + RANDOM("random"); - initQuerySelector(); - initLanguageProcessor(); + final String value; - this.hashcode = this.queryList.hashCode(); - } + Order(String value) { + this.value = value; + } - public void getNextQuery(StringBuilder queryStr, StringBuilder queryID) throws IOException { - int queryIndex = this.querySelector.getNextIndex(); - queryStr.append(this.queryList.getQuery(queryIndex)); - queryID.append(getQueryId(queryIndex)); - } + @JsonValue + public String value() { + return value; + } + } + + public enum Language { + @JsonEnumDefaultValue SPARQL("SPARQL"), + UNSPECIFIED("unspecified"); - public Model getTripleStats(String taskID) { - List queries = new ArrayList<>(this.queryList.size()); - for (int i = 0; i < this.queryList.size(); i++) { - try { - queries.add(new QueryWrapper(this.queryList.getQuery(i), getQueryId(i))); - } catch (Exception e) { - LOGGER.error("Could not parse query " + this.queryList.getName() + ":" + i, e); + final String value; + + Language(String value) { + this.value = value; + } + + @JsonValue + public String value() { + return value; } } - return this.langProcessor.generateTripleStats(queries, "" + this.hashcode, taskID); } - @Override - public int hashCode() { - return this.hashcode; - } + public record QueryStringWrapper(int index, String query) {} + public record QueryStreamWrapper(int index, InputStream queryInputStream) {} - public int getQueryCount() { - return this.queryList.size(); - } - public LanguageProcessor getLanguageProcessor() { - return this.langProcessor; - } + protected final Logger LOGGER = LoggerFactory.getLogger(QueryHandler.class); - /** - * This method initializes the PatternHandler if a pattern config is given, therefore - * this.config.get("pattern") should return an appropriate pattern configuration and not - * null. The PatternHandler uses the original query source to generate a new query source and list with - * the instantiated queries. - */ - private void initPatternQuerySet() { - Map patternConfig = (Map) this.config.get("pattern"); - PatternHandler patternHandler = new PatternHandler(patternConfig, createQuerySource()); + @JsonValue + final protected Config config; - initQuerySet(patternHandler.generateQuerySource()); - } + final protected QueryList queryList; + + private int workerCount = 0; // give every worker inside the same worker config an offset seed + + final protected int hashCode; /** - * Will initialize the QueryList. - * If caching is not set or set to true, the InMemQueryList will be used. Otherwise the FileBasedQueryList. - * - * @param querySource The QuerySource which contains the queries. + * Empty Constructor for Testing purposes. + * TODO: look for an alternative */ - private void initQuerySet(QuerySource querySource) { - this.caching = (Boolean) this.config.getOrDefault("caching", true); + protected QueryHandler() { + config = null; + queryList = null; + hashCode = 0; + } - if (this.caching) { - this.queryList = new InMemQueryList(this.location, querySource); - } else { - this.queryList = new FileBasedQueryList(this.location, querySource); - } + @JsonCreator + public QueryHandler(Config config) throws IOException { + final var querySource = switch (config.format()) { + case ONE_PER_LINE -> new FileLineQuerySource(Path.of(config.path())); + case SEPARATOR -> new FileSeparatorQuerySource(Path.of(config.path()), config.separator); + case FOLDER -> new FolderQuerySource(Path.of(config.path())); + }; + + queryList = (config.caching()) ? + new InMemQueryList(querySource) : + new FileBasedQueryList(querySource); + + this.config = config; + hashCode = queryList.hashCode(); } - /** - * This method initializes the QueryList for the QueryHandler. If a pattern configuration is specified, this method - * will execute initPatternQuerySet to create the QueryList. - */ - private void initQuerySet() { - if(this.config.containsKey("pattern")) { - initPatternQuerySet(); - } - else { - initQuerySet(createQuerySource()); + public QuerySelector getQuerySelectorInstance() { + switch (config.order()) { + case LINEAR -> { return new LinearQuerySelector(queryList.size()); } + case RANDOM -> { return new RandomQuerySelector(queryList.size(), config.seed() + workerCount++); } } + + throw new IllegalStateException("Unknown query selection order: " + config.order()); } - /** - * Will initialize the QuerySource. - * Depending on the format configuration, the FileLineQuerySource, - * FileSeparatorQuerySource or FolderQuerySource will be used. - * The FileSeparatorQuerySource can be further configured with a separator. - * - * @return The QuerySource which contains the queries. - */ - private QuerySource createQuerySource() { - Object formatObj = this.config.getOrDefault("format", "one-per-line"); - if (formatObj instanceof Map) { - Map format = (Map) formatObj; - if (format.containsKey("separator")) { - return new FileSeparatorQuerySource(this.location, (String) format.get("separator")); - } - } else { - switch ((String) formatObj) { - case "one-per-line": - return new FileLineQuerySource(this.location); - case "separator": - return new FileSeparatorQuerySource(this.location); - case "folder": - return new FolderQuerySource(this.location); - } - } - LOGGER.error("Could not create QuerySource for format {}", formatObj); - return null; + public QueryStringWrapper getNextQuery(QuerySelector querySelector) throws IOException { + final var queryIndex = querySelector.getNextIndex(); + return new QueryStringWrapper(queryIndex, queryList.getQuery(queryIndex)); } - /** - * Will initialize the QuerySelector that provides the next query index during the benchmark execution. - *

- * currently linear or random (with seed) are implemented - */ - private void initQuerySelector() { - Object orderObj = this.config.getOrDefault("order", "linear"); - - if (orderObj instanceof String) { - String order = (String) orderObj; - if (order.equals("linear")) { - this.querySelector = new LinearQuerySelector(this.queryList.size()); - return; - } - if (order.equals("random")) { - this.querySelector = new RandomQuerySelector(this.queryList.size(), this.workerID); - return; - } + public QueryStreamWrapper getNextQueryStream(QuerySelector querySelector) throws IOException { + final var queryIndex = querySelector.getNextIndex(); + return new QueryStreamWrapper(queryIndex, this.queryList.getQueryStream(queryIndex)); + } - LOGGER.error("Unknown order: " + order); - } - if (orderObj instanceof Map) { - Map order = (Map) orderObj; - if (order.containsKey("random")) { - Map random = (Map) order.get("random"); - Integer seed = (Integer) random.get("seed"); - this.querySelector = new RandomQuerySelector(this.queryList.size(), seed); - return; - } - LOGGER.error("Unknown order: " + order); - } + @Override + public int hashCode() { + return hashCode; } - private void initLanguageProcessor() { - Object langObj = this.config.getOrDefault("lang", "lang.SPARQL"); - if (langObj instanceof String) { - this.langProcessor = new TypedFactory().create((String) langObj, new HashMap<>()); - } else { - LOGGER.error("Unknown language: " + langObj); - } + public int getQueryCount() { + return this.queryList.size(); } public String getQueryId(int i) { - return this.queryList.getName() + ":" + i; + return this.queryList.hashCode() + ":" + i; } + /** + * Returns every query id in the format: queryListHash:index
+ * The index of a query inside the returned array is the same as the index inside the string. + * + * @return String[] of query ids + */ public String[] getAllQueryIds() { String[] out = new String[queryList.size()]; for (int i = 0; i < queryList.size(); i++) { diff --git a/src/main/java/org/aksw/iguana/cc/query/list/QueryList.java b/src/main/java/org/aksw/iguana/cc/query/list/QueryList.java index 39b0961cb..4006e917e 100644 --- a/src/main/java/org/aksw/iguana/cc/query/list/QueryList.java +++ b/src/main/java/org/aksw/iguana/cc/query/list/QueryList.java @@ -3,6 +3,7 @@ import org.aksw.iguana.cc.query.source.QuerySource; import java.io.IOException; +import java.io.InputStream; /** * The abstract class for a QueryList. A query list provides the queries to the QueryHandler. @@ -11,14 +12,12 @@ */ public abstract class QueryList { - /** This is the QuerySource from which the queries should be retrieved. */ - protected QuerySource querySource; - - /** A name for the query list. This is a part of the queryIDs. */ - protected String name; + /** + * This is the QuerySource from which the queries should be retrieved. + */ + final protected QuerySource querySource; - public QueryList(String name, QuerySource querySource) { - this.name = name; + public QueryList(QuerySource querySource) { this.querySource = querySource; } @@ -28,16 +27,7 @@ public QueryList(String name, QuerySource querySource) { * @return The amount of queries in the query list */ public int size() { - return this.querySource.size(); - } - - /** - * This method returns the name of the query list. - * - * @return The name of the query list - */ - public String getName() { - return this.name; + return querySource.size(); } /** @@ -47,7 +37,7 @@ public String getName() { */ @Override public int hashCode() { - return this.querySource.hashCode(); + return querySource.hashCode(); } /** @@ -57,4 +47,6 @@ public int hashCode() { * @return The query at the given index */ public abstract String getQuery(int index) throws IOException; + + public abstract InputStream getQueryStream(int index) throws IOException; } diff --git a/src/main/java/org/aksw/iguana/cc/query/list/impl/FileBasedQueryList.java b/src/main/java/org/aksw/iguana/cc/query/list/impl/FileBasedQueryList.java index 76d1ef459..f01c3ab63 100644 --- a/src/main/java/org/aksw/iguana/cc/query/list/impl/FileBasedQueryList.java +++ b/src/main/java/org/aksw/iguana/cc/query/list/impl/FileBasedQueryList.java @@ -4,6 +4,7 @@ import org.aksw.iguana.cc.query.source.QuerySource; import java.io.IOException; +import java.io.InputStream; /** * A query list which reads the queries directly from a file. @@ -12,12 +13,17 @@ */ public class FileBasedQueryList extends QueryList { - public FileBasedQueryList(String name, QuerySource querySource) { - super(name, querySource); + public FileBasedQueryList(QuerySource querySource) { + super(querySource); } @Override public String getQuery(int index) throws IOException { - return this.querySource.getQuery(index); + return querySource.getQuery(index); + } + + @Override + public InputStream getQueryStream(int index) throws IOException { + return querySource.getQueryStream(index); } } diff --git a/src/main/java/org/aksw/iguana/cc/query/list/impl/InMemQueryList.java b/src/main/java/org/aksw/iguana/cc/query/list/impl/InMemQueryList.java index d2cfb86b3..7e6d30a37 100644 --- a/src/main/java/org/aksw/iguana/cc/query/list/impl/InMemQueryList.java +++ b/src/main/java/org/aksw/iguana/cc/query/list/impl/InMemQueryList.java @@ -5,7 +5,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -18,24 +21,21 @@ public class InMemQueryList extends QueryList { private static final Logger LOGGER = LoggerFactory.getLogger(InMemQueryList.class); - private List queries; + private final List queries; - public InMemQueryList(String name, QuerySource querySource) { - super(name, querySource); - loadQueries(); + public InMemQueryList(QuerySource querySource) throws IOException { + super(querySource); + queries = this.querySource.getAllQueries().stream().map(s -> s.getBytes(StandardCharsets.UTF_8)).toList(); } - private void loadQueries() { - try { - this.queries = this.querySource.getAllQueries(); - } catch (IOException e) { - LOGGER.error("Could not read queries"); - } + @Override + public String getQuery(int index) { + return new String(this.queries.get(index), StandardCharsets.UTF_8); } @Override - public String getQuery(int index) { - return this.queries.get(index); + public InputStream getQueryStream(int index) { + return new ByteArrayInputStream(this.queries.get(index)); } @Override diff --git a/src/main/java/org/aksw/iguana/cc/query/pattern/PatternHandler.java b/src/main/java/org/aksw/iguana/cc/query/pattern/PatternHandler.java deleted file mode 100644 index 63cb1a907..000000000 --- a/src/main/java/org/aksw/iguana/cc/query/pattern/PatternHandler.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.aksw.iguana.cc.query.pattern; - -import org.aksw.iguana.cc.query.source.QuerySource; -import org.aksw.iguana.cc.query.source.impl.FileLineQuerySource; -import org.apache.jena.query.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.PrintWriter; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This class is used to instantiate SPARQL pattern queries.
- * It will create and execute a SPARQL query against the provided SPARQL endpoint, that will retrieve fitting values for - * the variables in the pattern query. - *

- * The instantiated queries are located in a text file, which is created at the given location. - * If a fitting query file is already present, the queries will not be instantiated again. - * - * @author frensing - */ -public class PatternHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(PatternHandler.class); - - private final Map config; - private final QuerySource querySource; - private String endpoint; - private Long limit; - private String outputFolder; - - public PatternHandler(Map config, QuerySource querySource) { - this.config = config; - this.querySource = querySource; - - init(); - } - - /** - * This method will generate the queries from the given patterns, write them - * to a file, and return a QuerySource based on that file. - * The QuerySource is then used in the QueryHandler to get the queries. - * - * @return QuerySource containing the instantiated queries - */ - public QuerySource generateQuerySource() { - File cacheFile = new File(this.outputFolder + File.separator + this.querySource.hashCode()); - if (cacheFile.exists()) { - - LOGGER.warn("Output file already exists. Will not generate queries again. To generate them new remove the {{}} file", cacheFile.getAbsolutePath()); - - } else { - LOGGER.info("Generating queries for pattern queries"); - File outFolder = new File(this.outputFolder); - if (!outFolder.exists()) { - if(!outFolder.mkdirs()) { - LOGGER.error("Failed to create folder for the generated queries"); - } - } - - try (PrintWriter pw = new PrintWriter(cacheFile)) { - for (int i = 0; i < this.querySource.size(); i++) { - for (String query : generateQueries(this.querySource.getQuery(i))) { - pw.println(query); - } - } - } catch (Exception e) { - LOGGER.error("Could not write to file", e); - } - } - - return new FileLineQuerySource(cacheFile.getAbsolutePath()); - } - - /** - * Initializes the PatternHandler - * Sets up the output folder, the endpoint and the limit. - */ - private void init() { - this.endpoint = (String) this.config.get("endpoint"); - if (this.endpoint == null) { - LOGGER.error("No endpoint given for pattern handler"); - } - - this.outputFolder = (String) this.config.getOrDefault("outputFolder", "queryCache"); - - Object limitObj = this.config.getOrDefault("limit", 2000L); - if (limitObj instanceof Number) { - this.limit = ((Number) limitObj).longValue(); - } else if (limitObj instanceof String) { - this.limit = Long.parseLong((String) limitObj); - } else { - LOGGER.error("could not parse limit"); - } - } - - /** - * This method generates a list of queries for a given pattern query. - * - * @param query String of the pattern query - * @return List of generated queries as strings - */ - protected List generateQueries(String query) { - List queries = new LinkedList<>(); - - try { - // if query is already an instance, we do not need to generate anything - QueryFactory.create(query); - LOGGER.debug("Query is already an instance: {{}}", query); - queries.add(query); - return queries; - } catch (Exception ignored) { - } - - // Replace the pattern variables with real variables and store them to the Set varNames - Set varNames = new HashSet<>(); - String command = replaceVars(query, varNames); - - // Generate parameterized sparql string to construct final queries - ParameterizedSparqlString pss = new ParameterizedSparqlString(); - pss.setCommandText(command); - - ResultSet exchange = getInstanceVars(pss, varNames); - - // exchange vars in PSS - if (!exchange.hasNext()) { - //no solution - LOGGER.warn("Pattern query has no solution, will use variables instead of var instances: {{}}", pss); - queries.add(command); - } - while (exchange.hasNext()) { - QuerySolution solution = exchange.next(); - for (String var : exchange.getResultVars()) { - //exchange variable with - pss.clearParam(var); - pss.setParam(var, solution.get(var)); - } - queries.add(pss.toString()); - } - LOGGER.debug("Found instances {}", queries); - - return queries; - } - - /** - * Replaces the pattern variables of the pattern query with actual variables and returns it. - * The names of the replaced variables will be stored in the set. - * - * @param queryStr String of the pattern query - * @param varNames This set will be extended by the strings of the replaced variable names - * @return The pattern query with the actual variables instead of pattern variables - */ - protected String replaceVars(String queryStr, Set varNames) { - String command = queryStr; - Pattern pattern = Pattern.compile("%%var[0-9]+%%"); - Matcher m = pattern.matcher(queryStr); - while (m.find()) { - String patternVariable = m.group(); - String var = patternVariable.replace("%", ""); - command = command.replace(patternVariable, "?" + var); - varNames.add(var); - } - return command; - } - - /** - * Generates valid values for the given variables in the query. - * - * @param pss The query, whose variables should be instantiated - * @param varNames The set of variables in the query that should be instantiated - * @return ResultSet that contains valid values for the given variables of the query - */ - protected ResultSet getInstanceVars(ParameterizedSparqlString pss, Set varNames) { - QueryExecution exec = QueryExecutionFactory.createServiceRequest(this.endpoint, convertToSelect(pss, varNames)); - //return result set - return exec.execSelect(); - } - - /** - * Creates a new query that can find valid values for the variables in the original query. - * The variables, that should be instantiated, are named by the set. - * - * @param pss The query whose variables should be instantiated - * @param varNames The set of variables in the given query that should be instantiated - * @return Query that can evaluate valid values for the given variables of the original query - */ - protected Query convertToSelect(ParameterizedSparqlString pss, Set varNames) { - Query queryCpy; - try { - if (varNames.isEmpty()) { - return pss.asQuery(); - } - queryCpy = pss.asQuery(); - } catch (Exception e) { - LOGGER.error("The pattern query is not a valid SELECT query (is it perhaps an UPDATE query?): {{}}", pss.toString(), e); - return null; - } - - StringBuilder queryStr = new StringBuilder("SELECT DISTINCT "); - for (String varName : varNames) { - queryStr.append("?").append(varName).append(" "); - } - queryStr.append(queryCpy.getQueryPattern()); - ParameterizedSparqlString pssSelect = new ParameterizedSparqlString(); - pssSelect.setCommandText(queryStr.toString()); - pssSelect.setNsPrefixes(pss.getNsPrefixMap()); - pssSelect.append(" LIMIT "); - pssSelect.append(this.limit); - return pssSelect.asQuery(); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/query/selector/QuerySelector.java b/src/main/java/org/aksw/iguana/cc/query/selector/QuerySelector.java index e66a954bc..824643213 100644 --- a/src/main/java/org/aksw/iguana/cc/query/selector/QuerySelector.java +++ b/src/main/java/org/aksw/iguana/cc/query/selector/QuerySelector.java @@ -1,17 +1,31 @@ package org.aksw.iguana.cc.query.selector; +import static java.text.MessageFormat.format; + /** * The QuerySelector provides a method to retrieve the index of a query, that should be executed next.
* It is used by the QueryHandler to get the next query. * * @author frensing */ -public interface QuerySelector { +public abstract class QuerySelector { + + protected int index = 0; + + protected final int size; + + public QuerySelector(int size) { + if (size <= 0) + throw new IllegalArgumentException(format("{0} size must be >0.", QuerySelector.class.getSimpleName())); + this.size = size; + } /** * This method gives the next query index that should be used. * * @return the next query index */ - int getNextIndex(); + public abstract int getNextIndex(); + + public abstract int getCurrentIndex(); } diff --git a/src/main/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelector.java b/src/main/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelector.java index a8a5abe37..3d3faad32 100644 --- a/src/main/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelector.java +++ b/src/main/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelector.java @@ -10,21 +10,29 @@ * * @author frensing */ -public class LinearQuerySelector implements QuerySelector { - - protected int querySelector; - - private int size; +public class LinearQuerySelector extends QuerySelector { public LinearQuerySelector(int size) { - this.size = size; + super(size); + index = -1; } @Override public int getNextIndex() { - if (this.querySelector >= this.size) { - this.querySelector = 0; + index++; + if (index >= this.size) { + index = 0; } - return this.querySelector++; + return index; + } + + /** + * Return the current index. This is the index of the last returned query. If no query was returned yet, it returns + * -1. + * @return + */ + @Override + public int getCurrentIndex() { + return index; } } diff --git a/src/main/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelector.java b/src/main/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelector.java index fdff4a248..80b18d51c 100644 --- a/src/main/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelector.java +++ b/src/main/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelector.java @@ -11,19 +11,23 @@ * * @author frensing */ -public class RandomQuerySelector implements QuerySelector { +public class RandomQuerySelector extends QuerySelector { - protected Random querySelector; - - private int size; + final protected Random indexGenerator; + int currentIndex; public RandomQuerySelector(int size, long seed) { - this.size = size; - this.querySelector = new Random(seed); + super(size); + indexGenerator = new Random(seed); } @Override public int getNextIndex() { - return this.querySelector.nextInt(this.size); + return currentIndex = this.indexGenerator.nextInt(this.size); + } + + @Override + public int getCurrentIndex() { + return currentIndex; } } diff --git a/src/main/java/org/aksw/iguana/cc/query/source/QuerySource.java b/src/main/java/org/aksw/iguana/cc/query/source/QuerySource.java index f505a8da6..38bdf966f 100644 --- a/src/main/java/org/aksw/iguana/cc/query/source/QuerySource.java +++ b/src/main/java/org/aksw/iguana/cc/query/source/QuerySource.java @@ -3,6 +3,8 @@ import org.aksw.iguana.cc.utils.FileUtils; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; import java.util.List; /** @@ -14,9 +16,9 @@ public abstract class QuerySource { /** This string represents the path of the file or folder, that contains the queries. */ - protected String path; + final protected Path path; - public QuerySource(String path) { + public QuerySource(Path path) { this.path = path; } @@ -36,6 +38,8 @@ public QuerySource(String path) { */ public abstract String getQuery(int index) throws IOException; + public abstract InputStream getQueryStream(int index) throws IOException; + /** * This method returns all queries in the source as a list of Strings. * diff --git a/src/main/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySource.java b/src/main/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySource.java index 60185e0fc..992df4384 100644 --- a/src/main/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySource.java +++ b/src/main/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySource.java @@ -1,45 +1,18 @@ package org.aksw.iguana.cc.query.source.impl; -import org.aksw.iguana.cc.query.source.QuerySource; -import org.aksw.iguana.cc.utils.IndexedQueryReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.aksw.iguana.cc.utils.FileUtils; import java.io.IOException; -import java.util.List; +import java.nio.file.Path; /** * The FileLineQuerySource reads queries from a file with one query per line. * * @author frensing */ -public class FileLineQuerySource extends QuerySource { - private static final Logger LOGGER = LoggerFactory.getLogger(FileLineQuerySource.class); - - private IndexedQueryReader iqr; - - public FileLineQuerySource(String path) { - super(path); - - try { - iqr = IndexedQueryReader.make(path); - } catch (IOException e) { - LOGGER.error("Failed to read this file for the queries: " + path + "\n" + e); - } - } - - @Override - public int size() { - return iqr.size(); - } - - @Override - public String getQuery(int index) throws IOException { - return iqr.readQuery(index); +public class FileLineQuerySource extends FileSeparatorQuerySource { + public FileLineQuerySource(Path filepath) throws IOException { + super(filepath, FileUtils.getLineEnding(filepath)); } - @Override - public List getAllQueries() throws IOException { - return iqr.readQueries(); - } } diff --git a/src/main/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySource.java b/src/main/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySource.java index 5b846be29..a0b07b10b 100644 --- a/src/main/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySource.java +++ b/src/main/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySource.java @@ -6,6 +6,8 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; import java.util.List; /** @@ -19,7 +21,7 @@ public class FileSeparatorQuerySource extends QuerySource { private static final String DEFAULT_SEPARATOR = "###"; - private IndexedQueryReader iqr; + final protected IndexedQueryReader iqr; /** * This constructor indexes the queries inside the given file. It assumes, that the queries inside the file are @@ -27,8 +29,9 @@ public class FileSeparatorQuerySource extends QuerySource { * * @param path path to the queries-file */ - public FileSeparatorQuerySource(String path) { - this(path, DEFAULT_SEPARATOR); + public FileSeparatorQuerySource(Path path) throws IOException { + super(path); + iqr = getIqr(path, DEFAULT_SEPARATOR); } /** @@ -39,19 +42,14 @@ public FileSeparatorQuerySource(String path) { * @param path path to the queries-file * @param separator string with which the queries inside the file are separated */ - public FileSeparatorQuerySource(String path, String separator) { + public FileSeparatorQuerySource(Path path, String separator) throws IOException { super(path); + iqr = getIqr(path, separator); + + } - try { - if(separator.isBlank()) { - iqr = IndexedQueryReader.makeWithEmptyLines(path); - } - else { - iqr = IndexedQueryReader.makeWithStringSeparator(path, separator); - } - } catch (IOException e) { - LOGGER.error("Failed to read this file for the queries: " + path + "\n" + e); - } + private static IndexedQueryReader getIqr(Path path, String separator) throws IOException { + return (separator.isEmpty()) ? IndexedQueryReader.makeWithEmptyLines(path) : IndexedQueryReader.makeWithStringSeparator(path, separator); } @Override @@ -64,6 +62,11 @@ public String getQuery(int index) throws IOException { return iqr.readQuery(index); } + @Override + public InputStream getQueryStream(int index) throws IOException { + return iqr.streamQuery(index); + } + @Override public List getAllQueries() throws IOException { return iqr.readQueries(); diff --git a/src/main/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySource.java b/src/main/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySource.java index 980c61ca1..04ae5fd12 100644 --- a/src/main/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySource.java +++ b/src/main/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySource.java @@ -2,14 +2,19 @@ import org.aksw.iguana.cc.query.source.QuerySource; import org.aksw.iguana.cc.utils.FileUtils; +import org.apache.commons.io.input.AutoCloseInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; + +import static java.text.MessageFormat.format; /** * The FileSeparatorQuerySource reads queries from a folder with query files. @@ -21,30 +26,26 @@ public class FolderQuerySource extends QuerySource { protected static final Logger LOGGER = LoggerFactory.getLogger(FolderQuerySource.class); - protected File[] files; + protected Path[] files; - public FolderQuerySource(String path) { + public FolderQuerySource(Path path) throws IOException { super(path); - indexFolder(); - } - - private void indexFolder() { - File dir = new File(this.path); - if (!dir.exists()) { - LOGGER.error("Folder does not exist"); - return; + if (!Files.isDirectory(path)) { + final var message = format("Folder does not exist {0}.", path); + LOGGER.error(message); + throw new IOException(message); } - if (!dir.isDirectory()) { - LOGGER.error("Path is not a folder"); - return; + + LOGGER.info("Indexing folder {}.", path); + + try (Stream pathStream = Files.list(path);) { + files = pathStream + .filter(p -> Files.isReadable(p) && Files.isRegularFile(p)) + .sorted() + .toArray(Path[]::new); } - LOGGER.info("indexing folder {}", this.path); - this.files = dir.listFiles(File::isFile); - if (this.files == null) - this.files = new File[]{}; - Arrays.sort(this.files); } @Override @@ -54,7 +55,12 @@ public int size() { @Override public String getQuery(int index) throws IOException { - return FileUtils.readFile(files[index].getAbsolutePath()); + return Files.readString(files[index], StandardCharsets.UTF_8); + } + + @Override + public InputStream getQueryStream(int index) throws IOException { + return new AutoCloseInputStream(new BufferedInputStream(new FileInputStream(files[index].toFile()))); } @Override @@ -68,6 +74,6 @@ public List getAllQueries() throws IOException { @Override public int hashCode() { - return FileUtils.getHashcodeFromFileContent(this.files[0].getAbsolutePath()); + return FileUtils.getHashcodeFromFileContent(this.files[0]); } } diff --git a/src/main/java/org/aksw/iguana/cc/storage/Storable.java b/src/main/java/org/aksw/iguana/cc/storage/Storable.java new file mode 100644 index 000000000..e45bcc976 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/storage/Storable.java @@ -0,0 +1,40 @@ +package org.aksw.iguana.cc.storage; + +import org.apache.jena.rdf.model.Model; + +import java.util.List; + +/** + * This interface provides the functionality to store data in different formats. The data can be stored in CSV files + * or in RDF models. + */ +public interface Storable { + + record CSVData ( + String folderName, + List files + ) { + public record CSVFileData(String filename, String[][] data) {} + } + + interface AsCSV extends Storable { + + /** + * Converts the data into CSV files. The key of the map contains the file name for the linked entries. + * + * @return CSVFileData list which contains all the files and their data that should be created and stored + */ + CSVData toCSV(); + } + + interface AsRDF extends Storable { + + /** + * Converts the data into an RDF model, which will be added to the appropriate storages. + * + * @return RDF model that contains the data + */ + Model toRDF(); + } + +} diff --git a/src/main/java/org/aksw/iguana/cc/storage/Storage.java b/src/main/java/org/aksw/iguana/cc/storage/Storage.java new file mode 100644 index 000000000..06d1c2234 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/storage/Storage.java @@ -0,0 +1,34 @@ +package org.aksw.iguana.cc.storage; + +import org.apache.jena.rdf.model.Model; + +/** + * Interface for the Result Storages + * + * @author f.conrads + * + */ +public interface Storage { + + /** + * Stores the task result into the storage. This method will be executed after a task has finished. + * Depending on the storages format, the storage class will need convert the data into the appropriate format. + * + * @param data the given result model + */ + void storeResult(Model data); + + /** + * General purpose method to store data into the storage. + * This method will mostly be used by the language processors to store their already formatted data.
+ * The default implementation will call the {@link #storeResult(Model)} method. This might not be the best solution + * for storages, that do not use RDF as their format. + * + * @param data the data to store + */ + default void storeData(Storable data) { + if (data instanceof Storable.AsRDF) { + storeResult(((Storable.AsRDF) data).toRDF()); + } + } +} diff --git a/src/main/java/org/aksw/iguana/cc/storage/impl/CSVStorage.java b/src/main/java/org/aksw/iguana/cc/storage/impl/CSVStorage.java new file mode 100644 index 000000000..4b37c439e --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/storage/impl/CSVStorage.java @@ -0,0 +1,417 @@ +package org.aksw.iguana.cc.storage.impl; + +import com.opencsv.CSVReader; +import com.opencsv.CSVWriter; +import com.opencsv.CSVWriterBuilder; +import com.opencsv.exceptions.CsvValidationException; +import org.aksw.iguana.cc.config.elements.StorageConfig; +import org.aksw.iguana.cc.metrics.*; +import org.aksw.iguana.cc.metrics.impl.AggregatedExecutionStatistics; +import org.aksw.iguana.cc.metrics.impl.EachExecutionStatistic; +import org.aksw.iguana.cc.storage.Storable; +import org.aksw.iguana.cc.storage.Storage; +import org.aksw.iguana.commons.rdf.IONT; +import org.aksw.iguana.commons.rdf.IPROP; +import org.apache.jena.arq.querybuilder.SelectBuilder; +import org.apache.jena.arq.querybuilder.WhereBuilder; +import org.apache.jena.query.*; +import org.apache.jena.rdf.model.*; +import org.apache.jena.sparql.lang.sparql_11.ParseException; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.*; +import java.util.*; +import java.util.function.Predicate; + +public class CSVStorage implements Storage { + + /** This private record is used to store information about the connections used in a task. */ + private record ConnectionInfo(String connection, String version, String dataset) {} + + public record Config(String directory) implements StorageConfig { + public Config(String directory) { + if (directory == null) { + directory = "results"; + } + Path path = Paths.get(directory); + if (Files.exists(path) && !Files.isDirectory(path)) { + throw new IllegalArgumentException("The given path is not a directory."); + } + this.directory = directory; + } + } + + private static final Logger LOGGER = LoggerFactory.getLogger(CSVStorage.class); + + private final List metrics; + + private final Path suiteFolder; + private Path currentFolder; + private final Path taskFile; + private final Path taskConfigFile; + + private List workerResources; + private Resource taskRes; + List connections; + + public CSVStorage(Config config, List metrics, String suiteID) { + this(config.directory(), metrics, suiteID); + } + + public CSVStorage(String folderPath, List metrics, String suiteID) { + this.metrics = metrics; + + Path parentFolder; + try { + parentFolder = Paths.get(folderPath); + } catch (InvalidPathException e) { + LOGGER.error("Can't store csv files, the given path is invalid.", e); + this.suiteFolder = null; + this.taskFile = null; + this.taskConfigFile = null; + return; + } + + this.suiteFolder = parentFolder.resolve("suite-" + suiteID); + this.taskFile = this.suiteFolder.resolve("suite-summary.csv"); + this.taskConfigFile = this.suiteFolder.resolve("task-configuration.csv"); + + if (Files.notExists(suiteFolder)) { + try { + Files.createDirectories(suiteFolder); + } catch (IOException e) { + LOGGER.error("Can't store csv files, directory could not be created.", e); + return; + } + } + + try { + Files.createFile(taskFile); + } catch (IOException e) { + LOGGER.error("Couldn't create the file: " + taskFile.toAbsolutePath(), e); + return; + } + + try { + Files.createFile(taskConfigFile); + } catch (IOException e) { + LOGGER.error("Couldn't create the file: " + taskFile.toAbsolutePath(), e); + return; + } + + // write headers for the suite-summary.csv file + try (CSVWriter csvWriter = getCSVWriter(taskFile)) { + Metric[] taskMetrics = metrics.stream().filter(x -> TaskMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); + List headerList = new LinkedList<>(); + // headerList.addAll(List.of("connection", "dataset", "startDate", "endDate", "noOfWorkers")); + headerList.addAll(List.of("taskID", "startDate", "endDate", "noOfWorkers")); + headerList.addAll(Arrays.stream(taskMetrics).map(Metric::getAbbreviation).toList()); + String[] header = headerList.toArray(String[]::new); + csvWriter.writeNext(header, true); + } catch (IOException e) { + LOGGER.error("Error while writing to file: " + taskFile.toAbsolutePath(), e); + } + + // write headers for the task-configuration.csv file + try (CSVWriter csvWriter = getCSVWriter(taskConfigFile)) { + csvWriter.writeNext(new String[]{"taskID", "connection", "version", "dataset"}, true); + } catch (IOException e) { + LOGGER.error("Error while writing to file: " + taskConfigFile.toAbsolutePath(), e); + } + } + + /** + * Stores the task result into the storage. This method will be executed after a task has finished. + * + * @param data the given result model + */ + @Override + public void storeResult(Model data) { + try { + setObjectAttributes(data); + } catch (NoSuchElementException e) { + LOGGER.error("Error while querying the result model. The given model is probably incorrect.", e); + return; + } + + this.currentFolder = this.suiteFolder.resolve("task-" + retrieveTaskID(this.taskRes)); + try { + Files.createDirectory(this.currentFolder); + } catch (IOException e) { + LOGGER.error("Error while storing the task result in a csv file.", e); + } + + try { + storeTaskInfo(); + storeTaskResults(data); + } catch (IOException e) { + LOGGER.error("Error while storing the task result in a csv file.", e); + } catch (NoSuchElementException | ParseException e) { + LOGGER.error("Error while storing the task result in a csv file. The given model is probably incorrect.", e); + } + + try { + Path temp = createCSVFile("worker", "summary"); + storeWorkerResults(this.taskRes, temp, data, this.metrics); + for (Resource workerRes : workerResources) { + String workerID = data.listObjectsOfProperty(workerRes, IPROP.workerID).next().asLiteral().getLexicalForm(); + try { + Path file = createCSVFile("query", "summary", "worker", workerID); + Path file2 = createCSVFile("each", "execution", "worker", workerID); + storeSummarizedQueryResults(workerRes, file, data, this.metrics); + storeEachQueryResults(workerRes, file2, data, this.metrics); + } catch (IOException e) { + LOGGER.error("Error while storing the query results of a worker in a csv file.", e); + } catch (NoSuchElementException e) { + LOGGER.error("Error while storing the query results of a worker in a csv file. The given model is probably incorrect.", e); + } + } + } catch (IOException e) { + LOGGER.error("Error while storing the worker results in a csv file.", e); + } catch (NoSuchElementException e) { + LOGGER.error("Error while storing the worker results in a csv file. The given model is probably incorrect.", e); + } + + try { + Path file = createCSVFile("query", "summary", "task"); + storeSummarizedQueryResults(taskRes, file, data, this.metrics); + } catch (IOException e) { + LOGGER.error("Error while storing the query results of a task result in a csv file.", e); + } catch (NoSuchElementException e) { + LOGGER.error("Error while storing the query results of a task result in a csv file. The given model is probably incorrect.", e); + } + } + + @Override + public void storeData(Storable data) { + if (!(data instanceof Storable.AsCSV)) return; // dismiss data if it can't be stored as csv + Storable.CSVData csvdata = ((Storable.AsCSV) data).toCSV(); + + Path responseTypeDir = Path.of(csvdata.folderName()); + responseTypeDir = this.currentFolder.resolve(responseTypeDir); + + try { + Files.createDirectory(responseTypeDir); + } catch (FileAlreadyExistsException ignored) { + } catch (IOException e) { + LOGGER.error("Error while creating the directory for the language processor results. ", e); + return; + } + + for (var csvFile : csvdata.files()) { + // check for file extension + String filename = csvFile.filename().endsWith(".csv") ? csvFile.filename() : csvFile.filename() + ".csv"; + Path file = responseTypeDir.resolve(filename); + + int i = 1; // skip the header by default + + if (Files.notExists(file)) { + try { + Files.createFile(file); + } catch (IOException e) { + LOGGER.error("Error while creating a csv file for language processor results. The storing of language processor results will be skipped.", e); + return; + } + i = 0; // include header if file is new + } + + try (CSVWriter writer = getCSVWriter(file)) { + for (; i < csvFile.data().length; i++) { + writer.writeNext(csvFile.data()[i], true); + } + } catch (IOException e) { + LOGGER.error("Error while writing the data into a csv file for language processor results. The storing of language processor results will be skipped.", e); + return; + } + } + } + + /** + * This method sets the objects attributes by querying the given model. + * + * @param data the result model + * @throws NoSuchElementException might be thrown if the model is incorrect + */ + private void setObjectAttributes(Model data) throws NoSuchElementException { + // obtain connection information of task + this.connections = new ArrayList<>(); + ResIterator resIterator = data.listSubjectsWithProperty(RDF.type, IONT.connection); + while (resIterator.hasNext()) { + Resource connectionRes = resIterator.nextResource(); + NodeIterator nodeIterator = data.listObjectsOfProperty(connectionRes, RDFS.label); + String conString = nodeIterator.next().asLiteral().getLexicalForm(); + + // obtain connection version + String conVersionString = ""; + nodeIterator = data.listObjectsOfProperty(connectionRes, IPROP.version); + if (nodeIterator.hasNext()) { + conVersionString = nodeIterator.next().toString(); + } + + // obtain dataset + String conDatasetString = ""; + nodeIterator = data.listObjectsOfProperty(connectionRes, IPROP.dataset); + if (nodeIterator.hasNext()) { + conDatasetString = nodeIterator.next().toString(); + } + this.connections.add(new ConnectionInfo(conString, conVersionString, conDatasetString)); + } + + // obtain task type + resIterator = data.listSubjectsWithProperty(RDF.type, IONT.task); + this.taskRes = resIterator.nextResource(); + + // obtain worker resources + NodeIterator nodeIterator = data.listObjectsOfProperty(this.taskRes, IPROP.workerResult); + this.workerResources = nodeIterator.toList().stream().map(RDFNode::asResource).toList(); + } + + /** + * Creates a CSV file with the given name values that will be located inside the parent folder. The name value are + * joined together with the character '-'. Empty values will be ignored. + * + * @param nameValues strings that build up the name of the file + * @throws IOException if an I/O error occurs + * @return path object to the created CSV file + */ + private Path createCSVFile(String... nameValues) throws IOException { + // remove empty string values + nameValues = Arrays.stream(nameValues).filter(Predicate.not(String::isEmpty)).toArray(String[]::new); + String filename = String.join("-", nameValues) + ".csv"; + Path file = this.currentFolder.resolve(filename); + Files.createFile(file); + return file; + } + + private static void storeSummarizedQueryResults(Resource parentRes, Path file, Model data, List metrics) throws IOException, NoSuchElementException { + boolean containsAggrStats = !metrics.stream().filter(AggregatedExecutionStatistics.class::isInstance).toList().isEmpty(); + Metric[] queryMetrics = metrics.stream().filter(x -> QueryMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); + + SelectBuilder sb = new SelectBuilder(); + sb.addWhere(parentRes, IPROP.query, "?eQ"); + queryProperties(sb, "?eQ", IPROP.queryID); + if (containsAggrStats) { + queryProperties(sb, "?eQ", IPROP.succeeded, IPROP.failed, IPROP.totalTime, IPROP.resultSize, IPROP.wrongCodes, IPROP.timeOuts, IPROP.unknownException); + } + queryMetrics(sb, "?eQ", queryMetrics); + + executeAndStoreQuery(sb, file, data); + } + + private static void storeEachQueryResults(Resource parentRes, Path file, Model data, List metrics) throws IOException { + boolean containsEachStats = !metrics.stream().filter(EachExecutionStatistic.class::isInstance).toList().isEmpty(); + if (!containsEachStats) { + return; + } + + SelectBuilder sb = new SelectBuilder(); + sb.addWhere(parentRes, IPROP.query, "?eQ") // variable name should be different from property names + .addWhere("?eQ", IPROP.queryExecution, "?exec") + .addOptional(new WhereBuilder().addWhere("?exec", IPROP.responseBody, "?rb").addWhere("?rb", IPROP.responseBodyHash, "?responseBodyHash")) + .addOptional(new WhereBuilder().addWhere("?exec", IPROP.exception, "?exception")) + .addOptional(new WhereBuilder().addWhere("?exec", IPROP.httpCode, "?httpCode")); + queryProperties(sb, "?exec", IPROP.queryID, IPROP.run, IPROP.success, IPROP.startTime, IPROP.time, IPROP.resultSize, IPROP.code); + sb.addVar("httpCode").addVar("exception").addVar("responseBodyHash"); + executeAndStoreQuery(sb, file, data); + } + + /** + * Stores the current task information into the task configuration file. + */ + private void storeTaskInfo() { + try (CSVWriter csvWriter = getCSVWriter(taskConfigFile)) { + for (ConnectionInfo connectionInfo : connections) { + csvWriter.writeNext(new String[]{this.taskRes.toString(), connectionInfo.connection(), connectionInfo.version(), connectionInfo.dataset()}, true); + } + } catch (IOException e) { + LOGGER.error("Error while writing to file: " + taskConfigFile.toAbsolutePath(), e); + } + } + + private void storeTaskResults(Model data) throws IOException, NoSuchElementException, ParseException { + Metric[] taskMetrics = metrics.stream().filter(x -> TaskMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); + + SelectBuilder sb = new SelectBuilder(); + queryProperties(sb, String.format("<%s>", this.taskRes.toString()), IPROP.startDate, IPROP.endDate, IPROP.noOfWorkers); + queryMetrics(sb, String.format("<%s>", this.taskRes.toString()), taskMetrics); + + try (QueryExecution exec = QueryExecutionFactory.create(sb.build(), data); + CSVWriter csvWriter = getCSVWriter(taskFile); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + ResultSet results = exec.execSelect(); + ResultSetFormatter.outputAsCSV(baos, results); + + // workaround to remove the created header from the ResultSetFormatter + CSVReader reader = new CSVReader(new StringReader(baos.toString())); + try { + reader.readNext(); + + // inject connection and dataset information + String[] row = reader.readNext(); + String[] newRow = new String[row.length + 1]; + newRow[0] = this.taskRes.getURI(); + // newRow[0] = connection; + // newRow[1] = dataset; + System.arraycopy(row, 0, newRow, 1, row.length); + csvWriter.writeNext(newRow, true); + } catch (CsvValidationException ignored) { + // shouldn't happen + } + } + } + + private static void storeWorkerResults(Resource taskRes, Path file, Model data, List metrics) throws IOException, NoSuchElementException { + Metric[] workerMetrics = metrics.stream().filter(x -> WorkerMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); + + SelectBuilder sb = new SelectBuilder(); + sb.addWhere(taskRes, IPROP.workerResult, "?worker"); + queryProperties(sb, "?worker", IPROP.workerID, IPROP.workerType, IPROP.noOfQueries, IPROP.timeOut, IPROP.startDate, IPROP.endDate); + queryMetrics(sb, "?worker", workerMetrics); + + executeAndStoreQuery(sb, file, data); + } + + private static CSVWriter getCSVWriter(Path file) throws IOException { + return (CSVWriter) new CSVWriterBuilder(new FileWriter(file.toAbsolutePath().toString(), true)) + .withQuoteChar('\"') + .withSeparator(',') + .withLineEnd("\n") + .build(); + } + + private static void queryProperties(SelectBuilder sb, String variable, Property... properties) { + for (Property prop : properties) { + sb.addVar(prop.getLocalName()).addWhere(variable, prop, "?" + prop.getLocalName()); + } + } + + private static void queryMetrics(SelectBuilder sb, String variable, Metric[] metrics) { + for (Metric m : metrics) { + // Optional, in case metric isn't created, because of failed executions + sb.addVar(m.getAbbreviation()).addOptional(variable, IPROP.createMetricProperty(m), "?" + m.getAbbreviation()); + } + } + + private static void executeAndStoreQuery(SelectBuilder sb, Path file, Model data) throws IOException { + try(QueryExecution exec = QueryExecutionFactory.create(sb.build(), data); + FileOutputStream fos = new FileOutputStream(file.toFile())) { + ResultSet results = exec.execSelect(); + ResultSetFormatter.outputAsCSV(fos, results); + } + } + + /** + * Retrieves the task ID from the given task resource. The current model doesn't save the task ID as a property of + * the task resource. Therefore, the task ID is extracted from the URI of the task resource. + * + * @param taskRes the task resource + * @return the task ID + */ + private static String retrieveTaskID(Resource taskRes) { + return taskRes.getURI().substring(taskRes.getURI().lastIndexOf("/") + 1); + } +} diff --git a/src/main/java/org/aksw/iguana/cc/storage/impl/RDFFileStorage.java b/src/main/java/org/aksw/iguana/cc/storage/impl/RDFFileStorage.java new file mode 100644 index 000000000..e3cce2801 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/storage/impl/RDFFileStorage.java @@ -0,0 +1,98 @@ +package org.aksw.iguana.cc.storage.impl; + +import com.github.jsonldjava.shaded.com.google.common.base.Supplier; +import org.aksw.iguana.cc.config.elements.StorageConfig; +import org.aksw.iguana.cc.storage.Storage; +import org.apache.commons.io.FilenameUtils; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.riot.RDFLanguages; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Calendar; +import java.util.Optional; + +public class RDFFileStorage implements Storage { + public record Config(String path) implements StorageConfig {} + + private static final Logger LOGGER = LoggerFactory.getLogger(RDFFileStorage.class.getName()); + + protected static Supplier defaultFileNameSupplier = () -> { + var now = Calendar.getInstance(); + return String.format("%d-%02d-%02d_%02d-%02d.%03d", + now.get(Calendar.YEAR), + now.get(Calendar.MONTH) + 1, + now.get(Calendar.DAY_OF_MONTH), + now.get(Calendar.HOUR_OF_DAY), + now.get(Calendar.MINUTE), + now.get(Calendar.MILLISECOND)); + }; + + final private Lang lang; + private Path path; + + public RDFFileStorage(Config config) { + this(config.path()); + } + + /** + * Uses a generated file called results_{DD}-{MM}-{YYYY}_{HH}-{mm}.ttl + */ + public RDFFileStorage() { + this(""); + } + + /** + * Uses the provided filename. If the filename is null or empty, a generated file called + * results_{DD}-{MM}-{YYYY}_{HH}-{mm}.ttl is used. The file extension determines the file format. + * + * @param fileName the filename to use + */ + public RDFFileStorage(String fileName) { + if (fileName == null || Optional.of(fileName).orElse("").isBlank()) { + path = Paths.get("").resolve(defaultFileNameSupplier.get() + ".ttl"); + } + else { + path = Paths.get(fileName); + if (Files.exists(path) && Files.isDirectory(path)) { + path = path.resolve(defaultFileNameSupplier.get() + ".ttl"); + } else if (Files.exists(path)) { + path = Paths.get(FilenameUtils.removeExtension(fileName) + "_" + defaultFileNameSupplier.get() + ".ttl"); // we're just going to assume that that's enough to make it unique + } + } + final var parentDir = path.toAbsolutePath().getParent(); + try { + Files.createDirectories(parentDir); + } catch (IOException e) { + LOGGER.error("Could not create parent directories for RDFFileStorage. ", e); + } + + this.lang = RDFLanguages.filenameToLang(path.toString(), Lang.TTL); + } + + @Override + public void storeResult(Model data){ + try (OutputStream os = new FileOutputStream(path.toString(), true)) { + RDFDataMgr.write(os, data, this.lang); + } catch (IOException e) { + LOGGER.error("Could not write to RDFFileStorage using lang: " + lang, e); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getFileName() { + return this.path.toString(); + } +} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/TriplestoreStorage.java b/src/main/java/org/aksw/iguana/cc/storage/impl/TriplestoreStorage.java similarity index 56% rename from src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/TriplestoreStorage.java rename to src/main/java/org/aksw/iguana/cc/storage/impl/TriplestoreStorage.java index 3623fc39a..994c24af2 100644 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/TriplestoreStorage.java +++ b/src/main/java/org/aksw/iguana/cc/storage/impl/TriplestoreStorage.java @@ -1,10 +1,8 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks.stresstest.storage.impl; +package org.aksw.iguana.cc.storage.impl; -import org.aksw.iguana.cc.tasks.stresstest.storage.TripleBasedStorage; -import org.aksw.iguana.commons.annotation.Shorthand; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.aksw.iguana.cc.config.elements.StorageConfig; +import org.aksw.iguana.cc.storage.Storage; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; @@ -25,70 +23,66 @@ /** * This Storage will save all the metric results into a specified triple store - * + * * @author f.conrads * */ -@Shorthand("TriplestoreStorage") -public class TriplestoreStorage extends TripleBasedStorage { - - private UpdateRequest blockRequest = UpdateFactory.create(); +public class TriplestoreStorage implements Storage { - - private final String updateEndpoint; + public record Config( + @JsonProperty(required = true) String endpoint, + String user, + String password, + String baseUri + ) implements StorageConfig {} + + private UpdateRequest blockRequest = UpdateFactory.create(); private final String endpoint; - private String user; - private String pwd; + private final String user; + private final String password; + private final String baseUri; - - public TriplestoreStorage(String endpoint, String updateEndpoint, String user, String pwd, String baseUri){ - this.endpoint=endpoint; - this.updateEndpoint=updateEndpoint; - this.user=user; - this.pwd=pwd; - if(baseUri!=null && !baseUri.isEmpty()){ - this.baseUri=baseUri; - } + public TriplestoreStorage(Config config) { + endpoint = config.endpoint(); + user = config.user(); + password = config.password(); + baseUri = config.baseUri(); } - - public TriplestoreStorage(String endpoint, String updateEndpoint, String baseUri){ - this.endpoint=endpoint; - this.updateEndpoint=updateEndpoint; - if(baseUri!=null && !baseUri.isEmpty()){ - this.baseUri=baseUri; - } + + + public TriplestoreStorage(String endpoint, String user, String pwd, String baseUri) { + this.endpoint = endpoint; + this.user = user; + this.password = pwd; + this.baseUri = baseUri; } - - public TriplestoreStorage(String endpoint, String updateEndpoint){ - this.endpoint=endpoint; - this.updateEndpoint=updateEndpoint; + + public TriplestoreStorage(String endpoint) { + this.endpoint = endpoint; + this.user = null; + this.password = null; + this.baseUri = null; } @Override public void storeResult(Model data) { - super.storeResult(data); - if (metricResults.size() == 0) - return; - StringWriter results = new StringWriter(); - RDFDataMgr.write(results, metricResults, Lang.NT); + RDFDataMgr.write(results, data, Lang.NT); String update = "INSERT DATA {" + results.toString() + "}"; //Create Update Request from block blockRequest.add(update); //submit Block to Triple Store UpdateProcessor processor = UpdateExecutionFactory - .createRemote(blockRequest, updateEndpoint, createHttpClient()); + .createRemote(blockRequest, endpoint, createHttpClient()); processor.execute(); blockRequest = new UpdateRequest(); } - - - private HttpClient createHttpClient(){ + private HttpClient createHttpClient() { CredentialsProvider credsProvider = new BasicCredentialsProvider(); - if(user !=null && pwd !=null){ - Credentials credentials = new UsernamePasswordCredentials(user, pwd); + if(user != null && password != null){ + Credentials credentials = new UsernamePasswordCredentials(user, password); credsProvider.setCredentials(AuthScope.ANY, credentials); } HttpClient httpclient = HttpClients.custom() diff --git a/src/main/java/org/aksw/iguana/cc/suite/IguanaSuiteParser.java b/src/main/java/org/aksw/iguana/cc/suite/IguanaSuiteParser.java new file mode 100644 index 000000000..8a297772d --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/suite/IguanaSuiteParser.java @@ -0,0 +1,260 @@ +package org.aksw.iguana.cc.suite; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import org.aksw.iguana.cc.config.elements.DatasetConfig; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Creates an IguanaConfig from a given JSON or YAML file, and validates the config using a JSON schema file + */ +public class IguanaSuiteParser { + private static final Logger LOGGER = LoggerFactory.getLogger(IguanaSuiteParser.class); + + private static final String SCHEMA_FILE = "./schema/iguana-schema.json"; + + enum DataFormat { + YAML, JSON; + + public static DataFormat getFormat(Path file) { + final var extension = FilenameUtils.getExtension(file.toString()); + switch (extension) { + case "yml", "yaml" -> { + return YAML; + } + case "json" -> { + return JSON; + } + default -> throw new IllegalStateException("Unexpected suite file extension: " + extension); + } + } + } + + /** + * Parses an IGUANA configuration file and optionally validates it against a JSON schema file, before parsing. + * + * @param config the path to the configuration file. + * @param validate whether to validate the configuration file against the JSON schema file. + * @return a Suite object containing the parsed configuration. + * @throws IOException if there is an error during IO. + * @throws IllegalStateException if the configuration file is invalid. + */ + public static Suite parse(Path config, boolean validate) throws IOException { + final var format = DataFormat.getFormat(config); + JsonFactory factory = switch (format) { + case YAML -> new YAMLFactory(); + case JSON -> new JsonFactory(); + }; + + if (validate && !validateConfig(config)) { + throw new IllegalStateException("Invalid config file"); + } + + try (var stream = new FileInputStream(config.toFile())) { + return parse(stream, factory); + } + } + + /** + * Validates an IGUANA configuration file against a JSON schema file. + * + * @param config the path to the configuration file. + * @return true if the configuration file is valid, false otherwise. + * @throws IOException if there is an error during IO. + */ + public static boolean validateConfig(Path config) throws IOException { + final var format = DataFormat.getFormat(config); + JsonFactory factory = switch (format) { + case YAML -> new YAMLFactory(); + case JSON -> new JsonFactory(); + }; + final var mapper = new ObjectMapper(factory); + + JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V6); + InputStream is = new FileInputStream(SCHEMA_FILE); + JsonSchema schema = schemaFactory.getSchema(is); + JsonNode node = mapper.readTree(config.toFile()); + Set errors = schema.validate(node); + if (!errors.isEmpty()) { + LOGGER.error("Found {} errors in configuration file.", errors.size()); + } + for (ValidationMessage message : errors) { + LOGGER.error(message.getMessage()); + } + return errors.isEmpty(); + } + + /** + * Parses an IGUANA configuration file.

+ * + * This involves two steps: First, datasets and connections are parsed and stored. In a second step, the rest of the + * file is parsed. If the names of datasets and connections are used, they are replaced with the respective + * configurations that were parsed in the first step. + * + * @param inputStream the input stream containing the configuration file content. + * @param factory the JsonFactory instance used for parsing the configuration file. + * @return a Suite object containing the parsed configuration. + * @throws IOException if there is an error during IO. + */ + private static Suite parse(InputStream inputStream, JsonFactory factory) throws IOException { + ObjectMapper mapper = new ObjectMapper(factory); + + final var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + final var datasets = preparseDataset(mapper, input); + + class DatasetDeserializer extends StdDeserializer { + public DatasetDeserializer() { + this(null); + } + + protected DatasetDeserializer(Class vc) { + super(vc); + } + + @Override + public DatasetConfig deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + if (node.isTextual()) { + final var datasetName = node.asText(); + if (!datasets.containsKey(datasetName)) + throw new IllegalStateException(MessageFormat.format("Unknown dataset name: {0}", datasetName)); + return datasets.get(datasetName); + } else { + DatasetConfig datasetConfig = ctxt.readValue(jp, DatasetConfig.class); + if (datasets.containsKey(datasetConfig.name())) + assert datasets.get(datasetConfig.name()) == datasetConfig; + else datasets.put(datasetConfig.name(), datasetConfig); + return datasetConfig; + } + } + } + mapper = new ObjectMapper(factory).registerModule(new SimpleModule() + .addDeserializer(DatasetConfig.class, new DatasetDeserializer())); + + final var connections = preparseConnections(mapper, input); + + class ConnectionDeserializer extends StdDeserializer { + + public ConnectionDeserializer() { + this(null); + } + + protected ConnectionDeserializer(Class vc) { + super(vc); + } + + @Override + public ConnectionConfig deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + if (node.isTextual()) { + final var connectionName = node.asText(); + if (!connections.containsKey(connectionName)) + throw new IllegalStateException(MessageFormat.format("Unknown connection name: {0}", connectionName)); + return connections.get(connectionName); + } else { + ConnectionConfig connectionConfig = ctxt.readValue(jp, ConnectionConfig.class); + if (connections.containsKey(connectionConfig.name())) + assert connections.get(connectionConfig.name()) == connectionConfig; + else connections.put(connectionConfig.name(), connectionConfig); + return connectionConfig; + } + } + } + + class HumanReadableDurationDeserializer extends StdDeserializer { + + public HumanReadableDurationDeserializer() { + this(null); + } + + protected HumanReadableDurationDeserializer(Class vc) { + super(vc); + } + + @Override + public Duration deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + var durationString = jp.getValueAsString() + .toLowerCase() + .replaceAll("\\s+", "") + .replace("years", "y") + .replace("year", "y") + .replace("months", "m") + .replace("month", "m") + .replace("weeks", "w") + .replace("week", "w") + .replace("days", "d") + .replace("day", "d") + .replace("mins", "m") + .replace("min", "m") + .replace("hrs", "h") + .replace("hr", "h") + .replace("secs", "s") + .replace("sec", "s") + .replaceFirst("(\\d+d)", "P$1T"); + if ((durationString.charAt(0) != 'P')) durationString = "PT" + durationString; + return Duration.parse(durationString); + } + } + + mapper = new ObjectMapper(factory).registerModule(new JavaTimeModule()) + .registerModule(new SimpleModule() + .addDeserializer(DatasetConfig.class, new DatasetDeserializer()) + .addDeserializer(ConnectionConfig.class, new ConnectionDeserializer()) + .addDeserializer(Duration.class, new HumanReadableDurationDeserializer())); + + final String suiteID = Instant.now().getEpochSecond() + "-" + Integer.toUnsignedString(input.hashCode()); // convert to unsigned, so that there is no double -- minus in the string + return new Suite(suiteID, mapper.readValue(input, Suite.Config.class)); + } + + /** + * Preparses the datasets field in a IGUANA configuration file and adds a custom Deserializer to mapper to enable retrieving already parsed datasets by name. + * + * @param mapper The ObjectMapper instance used for parsing the configuration file. + * @param input The input String containing the configuration file content. + * @return A Map of DatasetConfig objects, where the key is the dataset name and the value is the corresponding DatasetConfig object. + * @throws JsonProcessingException If there is an error during JSON processing. + */ + private static Map preparseDataset(ObjectMapper mapper, String input) throws JsonProcessingException { + @JsonIgnoreProperties(ignoreUnknown = true) + record PreparsingDatasets(@JsonProperty(required = true) List datasets) {} + final var preparsingDatasets = mapper.readValue(input, PreparsingDatasets.class); + + return preparsingDatasets.datasets().stream().collect(Collectors.toMap(DatasetConfig::name, Function.identity())); + } + + private static Map preparseConnections(ObjectMapper mapper, String input) throws JsonProcessingException { + @JsonIgnoreProperties(ignoreUnknown = true) + record PreparsingConnections(@JsonProperty(required = true) List connections) {} + final var preparsingConnections = mapper.readValue(input, PreparsingConnections.class); + + return preparsingConnections.connections().stream().collect(Collectors.toMap(ConnectionConfig::name, Function.identity())); + } + +} diff --git a/src/main/java/org/aksw/iguana/cc/suite/Suite.java b/src/main/java/org/aksw/iguana/cc/suite/Suite.java new file mode 100644 index 000000000..1cb38acb5 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/suite/Suite.java @@ -0,0 +1,103 @@ +package org.aksw.iguana.cc.suite; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import org.aksw.iguana.cc.config.elements.DatasetConfig; +import org.aksw.iguana.cc.config.elements.StorageConfig; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.impl.*; +import org.aksw.iguana.cc.storage.Storage; +import org.aksw.iguana.cc.storage.impl.CSVStorage; +import org.aksw.iguana.cc.storage.impl.RDFFileStorage; +import org.aksw.iguana.cc.storage.impl.TriplestoreStorage; +import org.aksw.iguana.cc.tasks.impl.Stresstest; +import org.aksw.iguana.cc.tasks.Task; +import org.aksw.iguana.cc.worker.ResponseBodyProcessor; +import org.aksw.iguana.cc.worker.ResponseBodyProcessorInstances; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class Suite { + + public record Config( + @JsonIgnore + List datasets, /* Will already be consumed and ignored herein */ + @JsonIgnore + List connections, /* Will already be consumed and ignored herein */ + @JsonProperty(required = true) + List tasks, + List storages, + List metrics, + @JsonProperty List responseBodyProcessors + ) {} + + + private final String suiteId; + private final Config config; + private final ResponseBodyProcessorInstances responseBodyProcessorInstances; + + private final static Logger LOGGER = LoggerFactory.getLogger(Suite.class); + + private final List tasks = new ArrayList<>(); + + Suite(String suiteId, Config config) { + this.suiteId = suiteId; + this.config = config; + long taskID = 0; + + responseBodyProcessorInstances = new ResponseBodyProcessorInstances(config.responseBodyProcessors); + List metrics = initialiseMetrics(this.config.metrics); + List storages = initialiseStorages(this.config.storages, metrics, this.suiteId); + + for (Task.Config task : config.tasks()) { + if (task instanceof Stresstest.Config) { + tasks.add(new Stresstest(this.suiteId, taskID++, (Stresstest.Config) task, responseBodyProcessorInstances, storages, metrics)); + } + } + } + + private static List initialiseMetrics(List metrics) { + if (metrics != null && !metrics.isEmpty()) { + return metrics; + } + + final List out = new ArrayList<>(); + out.add(new QPS()); + out.add(new AvgQPS()); + out.add(new NoQPH()); + out.add(new AggregatedExecutionStatistics()); + out.add(new EachExecutionStatistic()); + out.add(new NoQ()); + out.add(new QMPH()); + return out; + } + + private static List initialiseStorages(List configs, List metrics, String suiteID) { + List out = new ArrayList<>(); + for (var storageConfig : configs) { + if (storageConfig instanceof CSVStorage.Config) { + out.add(new CSVStorage((CSVStorage.Config) storageConfig, metrics, suiteID)); + } + else if (storageConfig instanceof TriplestoreStorage.Config) { + out.add(new TriplestoreStorage((TriplestoreStorage.Config) storageConfig)); + } + else if (storageConfig instanceof RDFFileStorage.Config) { + out.add(new RDFFileStorage((RDFFileStorage.Config) storageConfig)); + } + } + return out; + } + + public void run() { + for (int i = 0; i < tasks.size(); i++) { + tasks.get(i).run(); + LOGGER.info("Task/{} {} finished.", tasks.get(i).getTaskName(), i); + } + } +} + + diff --git a/src/main/java/org/aksw/iguana/cc/tasks/AbstractTask.java b/src/main/java/org/aksw/iguana/cc/tasks/AbstractTask.java deleted file mode 100644 index ae9de8d43..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/AbstractTask.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import java.util.Properties; - -/** - * Abstract Task to help create a Task. - * Will do the background work - * - * @author f.conrads - * - */ -public abstract class AbstractTask implements Task { - - protected String taskID; - protected ConnectionConfig con; - - protected String expID; - protected String suiteID; - protected String datasetID; - protected String conID; - protected String taskName; - - /** - * Creates an AbstractTask with the TaskID - */ - public AbstractTask() { - - } - - - /* - * (non-Javadoc) - * - * @see org.aksw.iguana.tp.tasks.Task#init() - */ - @Override - public void init(String[] ids, String dataset, ConnectionConfig con, String taskName) { - this.suiteID=ids[0]; - this.expID=ids[1]; - this.taskID=ids[2]; - this.taskName=taskName; - this.datasetID=dataset; - this.conID=con.getName(); - this.con=con; - } - - @Override - public void start() {} - - @Override - public void sendResults(Properties data) {} - - @Override - public void close() {} - - @Override - public void addMetaData() {} - -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/Task.java b/src/main/java/org/aksw/iguana/cc/tasks/Task.java index 3bd6b0b7a..5c5901fcc 100644 --- a/src/main/java/org/aksw/iguana/cc/tasks/Task.java +++ b/src/main/java/org/aksw/iguana/cc/tasks/Task.java @@ -1,66 +1,18 @@ -/** - * - */ package org.aksw.iguana.cc.tasks; -import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.aksw.iguana.cc.tasks.impl.Stresstest; -import java.io.IOException; -import java.util.Properties; - -/** - * A simple Task to execute - * - * @author f.conrads - * - */ public interface Task { - - /** - * Will execute the Task - */ - public void execute(); - - /** - * Will start the Task (sending the rabbitMQ start flag) - */ - public void start(); - - /** - * Will send the results to the result processing. - * @param data - * @throws IOException - */ - void sendResults(Properties data) throws IOException; - - - /** - * Will close the Task and post process everything (e.g. send the end flag to the rabbit mq queue) - */ - void close(); - - /** - * Will add the Meta data for the start which then can be saved into the triple based storages - */ - void addMetaData(); - - - /** - * Will initialize the task - * @param ids normally the suiteID, experimentID, taskID - * @param dataset the dataset name - * @param con the current connection to execute the task against - * @param taskName the taskName - */ - void init(String[] ids, String dataset, ConnectionConfig con, String taskName); - - /** - * Will initialize the task - * @param ids normally the suiteID, experimentID, taskID - * @param dataset the dataset name - * @param con the current connection to execute the task against - */ - default void init(String[] ids, String dataset, ConnectionConfig con){ - init(ids, dataset, con, null); - } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = Stresstest.Config.class, name = "stresstest"), + }) + interface Config {} + + void run(); + String getTaskName(); } diff --git a/src/main/java/org/aksw/iguana/cc/tasks/TaskFactory.java b/src/main/java/org/aksw/iguana/cc/tasks/TaskFactory.java deleted file mode 100644 index bf753ef1b..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/TaskFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks; - -import org.aksw.iguana.commons.factory.TypedFactory; - - -/** - * Factory to create Tasks. see {@link TypedFactory} for more information. - * - * @author f.conrads - * - */ -public class TaskFactory extends TypedFactory{ - - -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/TaskManager.java b/src/main/java/org/aksw/iguana/cc/tasks/TaskManager.java deleted file mode 100644 index 22e8b8605..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/TaskManager.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -/** - * Will manage the Tasks - * - * @author f.conrads - * - */ -public class TaskManager { - - private Task task; - - /** - * Will simply set the Task to execute - * @param task - */ - public void setTask(Task task){ - this.task = task; - } - - /** - * Will initialize and start the Task - * - * @throws IOException - * @throws TimeoutException - */ - public void startTask(String[] ids, String dataset, ConnectionConfig con, String taskName) throws IOException, TimeoutException{ - this.task.init(ids, dataset, con, taskName); - this.task.addMetaData(); - this.task.start(); - this.task.execute(); - this.task.close(); - } - - - -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/impl/Stresstest.java b/src/main/java/org/aksw/iguana/cc/tasks/impl/Stresstest.java new file mode 100644 index 000000000..cfa03d243 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/tasks/impl/Stresstest.java @@ -0,0 +1,111 @@ +package org.aksw.iguana.cc.tasks.impl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.storage.Storage; +import org.aksw.iguana.cc.tasks.Task; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.cc.worker.ResponseBodyProcessorInstances; +import org.aksw.iguana.cc.worker.impl.SPARQLProtocolWorker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.*; + + +/** + * Stresstest. + * Will stresstest a connection using several Workers (simulated Users) each in one thread. + */ +public class Stresstest implements Task { + + public record Config( + List warmupWorkers, + @JsonProperty(required = true) List workers + ) implements Task.Config {} + + public record Result( + List workerResults, + Calendar startTime, + Calendar endTime + ) {} + + + private static final Logger LOGGER = LoggerFactory.getLogger(Stresstest.class); + + private final List warmupWorkers = new ArrayList<>(); + private final List workers = new ArrayList<>(); + + private final StresstestResultProcessor srp; + + + public Stresstest(String suiteID, long stresstestID, Config config, ResponseBodyProcessorInstances responseBodyProcessorInstances, List storages, List metrics) { + + // initialize workers + long workerId = 0; + if (config.warmupWorkers() != null) { + for (HttpWorker.Config workerConfig : config.warmupWorkers()) { + for (int i = 0; i < workerConfig.number(); i++) { + var responseBodyProcessor = (workerConfig.parseResults()) ? responseBodyProcessorInstances.getProcessor(workerConfig.acceptHeader()) : null; + warmupWorkers.add(new SPARQLProtocolWorker(workerId++, responseBodyProcessor, (SPARQLProtocolWorker.Config) workerConfig)); + } + } + } + + for (HttpWorker.Config workerConfig : config.workers()) { + for (int i = 0; i < workerConfig.number(); i++) { + var responseBodyProcessor = (workerConfig.parseResults()) ? responseBodyProcessorInstances.getProcessor(workerConfig.acceptHeader()) : null; + workers.add(new SPARQLProtocolWorker(workerId++, responseBodyProcessor, (SPARQLProtocolWorker.Config) workerConfig)); + } + } + + // retrieve all query ids + Set queryIDs = new HashSet<>(); + for (HttpWorker.Config wConfig : config.workers) { + if (wConfig instanceof SPARQLProtocolWorker.Config) { + queryIDs.addAll(List.of((wConfig).queries().getAllQueryIds())); + } + } + + srp = new StresstestResultProcessor( + suiteID, + stresstestID, + this.workers, + new ArrayList<>(queryIDs), + metrics, + storages, + responseBodyProcessorInstances.getResults() + ); + } + + public void run() { + var warmupResults = executeWorkers(warmupWorkers); // warmup results will be dismissed + var results = executeWorkers(workers); + + srp.process(results.workerResults); + srp.calculateAndSaveMetrics(results.startTime, results.endTime); + } + + private Result executeWorkers(List workers) { + List results = new ArrayList<>(workers.size()); + Calendar startTime = Calendar.getInstance(); // TODO: Calendar is outdated + var futures = workers.stream().map(HttpWorker::start).toList(); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + Calendar endTime = Calendar.getInstance(); // TODO: add start and end time for each worker + for (CompletableFuture future : futures) { + try { + results.add(future.get()); + } catch (ExecutionException e) { + LOGGER.error("Unexpected error during execution of worker.", e); + } catch (InterruptedException ignored) {} + + } + return new Result(results, startTime, endTime); + } + + @Override + public String getTaskName() { + return "stresstest"; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/impl/StresstestResultProcessor.java b/src/main/java/org/aksw/iguana/cc/tasks/impl/StresstestResultProcessor.java new file mode 100644 index 000000000..26f21a2af --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/tasks/impl/StresstestResultProcessor.java @@ -0,0 +1,280 @@ +package org.aksw.iguana.cc.tasks.impl; + +import org.aksw.iguana.cc.lang.LanguageProcessor; +import org.aksw.iguana.cc.metrics.*; +import org.aksw.iguana.cc.storage.Storage; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.commons.rdf.IGUANA_BASE; +import org.aksw.iguana.commons.rdf.IONT; +import org.aksw.iguana.commons.rdf.IPROP; +import org.aksw.iguana.commons.rdf.IRES; +import org.aksw.iguana.commons.time.TimeUtils; +import org.apache.jena.rdf.model.*; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; + +import java.time.ZonedDateTime; +import java.util.*; +import java.util.function.Supplier; + +public class StresstestResultProcessor { + + private record StartEndTimePair ( + ZonedDateTime startTime, + ZonedDateTime endTime + ) {} + + private final List metrics; + private final List workers; + private final List queryIDs; + private final List storages; + private final Supplier>> lpResults; + + /** + * This array contains each query execution of a worker grouped to its queries. + * The outer array is indexed with the workerID and the inner array with the numeric queryID that the query has + * inside that worker. + */ + private final List[][] workerQueryExecutions; + + /** This map contains each query execution, grouped by each queryID of the task. */ + private final Map> taskQueryExecutions; + + + /** Stores the start and end time for each workerID. */ + private final StartEndTimePair[] workerStartEndTime; + + private final IRES.Factory iresFactory; + + + public StresstestResultProcessor(String suiteID, + long taskID, + List worker, + List queryIDs, + List metrics, + List storages, + Supplier>> lpResults) { + this.workers = worker; + this.queryIDs = queryIDs; + this.storages = storages; + this.metrics = metrics; + this.lpResults = lpResults; + + this.workerQueryExecutions = new ArrayList[workers.size()][]; + for (int i = 0; i < workers.size(); i++) { + this.workerQueryExecutions[i] = new ArrayList[workers.get(i).config().queries().getQueryCount()]; + for (int j = 0; j < workers.get(i).config().queries().getQueryCount(); j++) { + this.workerQueryExecutions[i][j] = new ArrayList<>(); + } + } + + this.taskQueryExecutions = new HashMap<>(); + for (String queryID : queryIDs) { + this.taskQueryExecutions.put(queryID, new ArrayList<>()); + } + + this.iresFactory = new IRES.Factory(suiteID, taskID); + this.workerStartEndTime = new StartEndTimePair[worker.size()]; + } + + /** + * This method stores the given query executions statistics from a worker to their appropriate data location. + * + * @param data the query execution statistics that should be stored + */ + public void process(Collection data) { + for (HttpWorker.Result result : data) { + for (var stat : result.executionStats()) { + workerQueryExecutions[(int) result.workerID()][stat.queryID()].add(stat); + String queryID = workers.get((int) result.workerID()).config().queries().getQueryId(stat.queryID()); + taskQueryExecutions.get(queryID).add(stat); + } + workerStartEndTime[Math.toIntExact(result.workerID())] = new StartEndTimePair(result.startTime(), result.endTime()); // Naively assumes that there won't be more than Integer.MAX workers + } + } + + /** + * This method calculates the metrics and creates the RDF model of the result, which will be sent to the storages. + * It uses the given data that was passed with the 'processQueryExecutions' method. + * + * @param start the start date of the task + * @param end the end date of the task + */ + public void calculateAndSaveMetrics(Calendar start, Calendar end) { + Model m = ModelFactory.createDefaultModel().setNsPrefixes(IGUANA_BASE.PREFIX_MAP); + Resource suiteRes = iresFactory.getSuiteResource(); + Resource taskRes = iresFactory.getTaskResource(); + + m.add(suiteRes, RDF.type, IONT.suite); + m.add(suiteRes, IPROP.task, taskRes); + m.add(taskRes, RDF.type, IONT.task); + m.add(taskRes, RDF.type, IONT.stresstest); + m.add(taskRes, IPROP.noOfWorkers, ResourceFactory.createTypedLiteral(workers.size())); + + for (HttpWorker worker : workers) { + HttpWorker.Config config = worker.config(); + + Resource workerRes = iresFactory.getWorkerResource(worker); + Resource connectionRes = IRES.getResource(config.connection().name()); + if (config.connection().dataset() != null) { + Resource datasetRes = IRES.getResource(config.connection().dataset().name()); + m.add(connectionRes, IPROP.dataset, datasetRes); + m.add(datasetRes, RDFS.label, ResourceFactory.createTypedLiteral(config.connection().dataset().name())); + m.add(datasetRes, RDF.type, IONT.dataset); + } + + m.add(taskRes, IPROP.workerResult, workerRes); + m.add(workerRes, RDF.type, IONT.worker); + m.add(workerRes, IPROP.workerID, ResourceFactory.createTypedLiteral(worker.getWorkerID())); + m.add(workerRes, IPROP.workerType, ResourceFactory.createTypedLiteral(worker.getClass().getSimpleName())); + m.add(workerRes, IPROP.noOfQueries, ResourceFactory.createTypedLiteral(config.queries().getQueryCount())); + m.add(workerRes, IPROP.timeOut, TimeUtils.createTypedDurationLiteral(config.timeout())); + if (config.completionTarget() instanceof HttpWorker.QueryMixes) + m.add(taskRes, IPROP.noOfQueryMixes, ResourceFactory.createTypedLiteral(((HttpWorker.QueryMixes) config.completionTarget()).number())); + if (config.completionTarget() instanceof HttpWorker.TimeLimit) + m.add(taskRes, IPROP.timeLimit, TimeUtils.createTypedDurationLiteral(((HttpWorker.TimeLimit) config.completionTarget()).duration())); + m.add(workerRes, IPROP.connection, connectionRes); + + m.add(connectionRes, RDF.type, IONT.connection); + m.add(connectionRes, RDFS.label, ResourceFactory.createTypedLiteral(config.connection().name())); + if (config.connection().version() != null) { + m.add(connectionRes, IPROP.version, ResourceFactory.createTypedLiteral(config.connection().version())); + } + } + + // Connect task and workers to the Query nodes, that store the triple stats. + for (var worker : workers) { + var config = worker.config(); + var workerQueryIDs = config.queries().getAllQueryIds(); + for (int i = 0; i < config.queries().getQueryCount(); i++) { + Resource workerQueryRes = iresFactory.getWorkerQueryResource(worker, i); + Resource queryRes = IRES.getResource(workerQueryIDs[i]); + m.add(workerQueryRes, IPROP.queryID, queryRes); + } + + var taskQueryIDs = this.queryIDs.toArray(String[]::new); // make elements accessible by index + for (String taskQueryID : taskQueryIDs) { + Resource taskQueryRes = iresFactory.getTaskQueryResource(taskQueryID); + Resource queryRes = IRES.getResource(taskQueryID); + m.add(taskQueryRes, IPROP.queryID, queryRes); + } + } + + for (Metric metric : metrics) { + m.add(this.createMetricModel(metric)); + } + + // Task to queries + for (String queryID : queryIDs) { + m.add(taskRes, IPROP.query, iresFactory.getTaskQueryResource(queryID)); + } + + for (var worker : workers) { + Resource workerRes = iresFactory.getWorkerResource(worker); + + // Worker to queries + for (int i = 0; i < worker.config().queries().getAllQueryIds().length; i++) { + m.add(workerRes, IPROP.query, iresFactory.getWorkerQueryResource(worker, i)); + } + + // start and end times for the workers + final var timePair = workerStartEndTime[Math.toIntExact(worker.getWorkerID())]; + m.add(workerRes, IPROP.startDate, TimeUtils.createTypedZonedDateTimeLiteral(timePair.startTime)); + m.add(workerRes, IPROP.endDate, TimeUtils.createTypedZonedDateTimeLiteral(timePair.endTime)); + } + + m.add(taskRes, IPROP.startDate, ResourceFactory.createTypedLiteral(start)); + m.add(taskRes, IPROP.endDate, ResourceFactory.createTypedLiteral(end)); + + for (var storage : storages) { + storage.storeResult(m); + } + + // Store results of language processors (this shouldn't throw an error if the map is empty) + for (var languageProcessor: lpResults.get().keySet()) { + for (var data : lpResults.get().get(languageProcessor)) { + for (var storage : storages) { + storage.storeData(data); + } + } + } + } + + /** + * For a given metric this method calculates the metric with the stored data and creates the appropriate + * RDF related to that metric. + * + * @param metric the metric that should be calculated + * @return the result model of the metric + */ + private Model createMetricModel(Metric metric) { + Model m = ModelFactory.createDefaultModel(); + Property metricProp = IPROP.createMetricProperty(metric); + Resource metricRes = IRES.getMetricResource(metric); + Resource taskRes = iresFactory.getTaskResource(); + + if (metric instanceof ModelWritingMetric) { + m.add(((ModelWritingMetric) metric).createMetricModel(this.workers, + this.workerQueryExecutions, + this.iresFactory)); + m.add(((ModelWritingMetric) metric).createMetricModel(this.workers, + this.taskQueryExecutions, + this.iresFactory)); + } + + if (metric instanceof TaskMetric) { + Number metricValue = ((TaskMetric) metric).calculateTaskMetric(this.workers, workerQueryExecutions); + if (metricValue != null) { + Literal lit = ResourceFactory.createTypedLiteral(metricValue); + m.add(taskRes, metricProp, lit); + } + m.add(taskRes, IPROP.metric, metricRes); + } + + if (metric instanceof WorkerMetric) { + for (var worker : workers) { + Resource workerRes = iresFactory.getWorkerResource(worker); + Number metricValue = ((WorkerMetric) metric).calculateWorkerMetric( + worker.config(), + workerQueryExecutions[(int) worker.getWorkerID()]); + if (metricValue != null) { + Literal lit = ResourceFactory.createTypedLiteral(metricValue); + m.add(workerRes, metricProp, lit); + } + m.add(workerRes, IPROP.metric, metricRes); + } + } + + if (metric instanceof QueryMetric) { + // queries grouped by worker + for (var worker : workers) { + for (int i = 0; i < worker.config().queries().getQueryCount(); i++) { + Number metricValue = ((QueryMetric) metric).calculateQueryMetric(workerQueryExecutions[(int) worker.getWorkerID()][i]); + if (metricValue != null) { + Literal lit = ResourceFactory.createTypedLiteral(metricValue); + Resource queryRes = iresFactory.getWorkerQueryResource(worker, i); + m.add(queryRes, metricProp, lit); + } + } + } + + // queries grouped by task + for (String queryID : queryIDs) { + Number metricValue = ((QueryMetric) metric).calculateQueryMetric(taskQueryExecutions.get(queryID)); + if (metricValue != null) { + Literal lit = ResourceFactory.createTypedLiteral(metricValue); + Resource queryRes = iresFactory.getTaskQueryResource(queryID); + m.add(queryRes, metricProp, lit); + } + } + } + + m.add(metricRes, RDFS.label, metric.getName()); + m.add(metricRes, RDFS.label, metric.getAbbreviation()); + m.add(metricRes, RDFS.comment, metric.getDescription()); + m.add(metricRes, RDF.type, IONT.getMetricClass(metric)); + m.add(metricRes, RDF.type, IONT.metric); + + return m; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/Stresstest.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/Stresstest.java deleted file mode 100644 index ae84cae64..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/Stresstest.java +++ /dev/null @@ -1,364 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.AbstractTask; -import org.aksw.iguana.cc.worker.Worker; -import org.aksw.iguana.cc.worker.WorkerFactory; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.riot.RDFDataMgr; -import org.apache.jena.riot.RDFFormat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.StringWriter; -import java.time.Instant; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import static org.aksw.iguana.commons.time.TimeUtils.durationInMilliseconds; - - -/** - * Stresstest. - * Will stresstest a connection using several Workers (simulated Users) each in one thread. - */ -@Shorthand("Stresstest") -public class Stresstest extends AbstractTask { - private static final Logger LOGGER = LoggerFactory.getLogger(Stresstest.class); - - private final Map warmupConfig; - private final List warmupWorkers = new ArrayList<>(); - private final List> workerConfig; - protected List workers = new LinkedList<>(); - private Double warmupTimeMS; - private Double timeLimit; - private Long noOfQueryMixes; - private Instant startTime; - - private StresstestResultProcessor rp; - - private Calendar startDate; - private Calendar endDate; - - public Stresstest(Integer timeLimit, List> workers) { - this(timeLimit, workers, null); - } - - public Stresstest(Integer timeLimit, List> workers, Map warmup) { - this.timeLimit = timeLimit.doubleValue(); - this.workerConfig = workers; - this.warmupConfig = warmup; - } - - public Stresstest(List> workers, Integer noOfQueryMixes) { - this(workers, null, noOfQueryMixes); - } - - public Stresstest(List> workers, Map warmup, Integer noOfQueryMixes) { - this.noOfQueryMixes = noOfQueryMixes.longValue(); - this.workerConfig = workers; - this.warmupConfig = warmup; - } - - private void initWorkers() { - if (this.warmupConfig != null) { - createWarmupWorkers(); - } - createWorkers(); - } - - private void createWarmupWorkers() { - this.warmupTimeMS = ((Integer) this.warmupConfig.get("timeLimit")).doubleValue(); - - List> warmupWorkerConfig = (List>) this.warmupConfig.get("workers"); - createWorkers(warmupWorkerConfig, this.warmupWorkers, this.warmupTimeMS); - } - - private void createWorkers() { - createWorkers(this.workerConfig, this.workers, this.timeLimit); - } - - private void createWorkers(List> workers, List workersToAddTo, Double timeLimit) { - int workerID = 0; - for (Map workerConfig : workers) { - workerID += createWorker(workerConfig, workersToAddTo, timeLimit, workerID); - } - } - - private int createWorker(Map workerConfig, List workersToAddTo, Double timeLimit, Integer baseID) { - //let TypedFactory create from className and configuration - String className = workerConfig.remove("className").toString(); - //if shorthand classname is used, exchange to full classname - workerConfig.put("connection", this.con); - workerConfig.put("taskID", this.taskID); - - if (timeLimit != null) { - workerConfig.put("timeLimit", timeLimit.intValue()); - } - Integer threads = (Integer) workerConfig.remove("threads"); - for (int i = 0; i < threads; i++) { - workerConfig.put("workerID", baseID + i); - Worker worker = new WorkerFactory().create(className, workerConfig); - if (this.noOfQueryMixes != null) { - worker.endAtNoOfQueryMixes(this.noOfQueryMixes); - } - workersToAddTo.add(worker); - } - return threads; - } - - public void generateTripleStats() { - StringWriter sw = new StringWriter(); - Model tripleStats = ModelFactory.createDefaultModel(); - // TODO: workers might have the same queries, the following code thus adds unnecessary redundancy - for (Worker worker : this.workers) { - tripleStats.add(worker.getQueryHandler().getTripleStats(this.taskID)); - } - RDFDataMgr.write(sw, tripleStats, RDFFormat.NTRIPLES); - } - - /** - * Add extra Meta Data - */ - @Override - public void addMetaData() {} - - - @Override - public void init(String[] ids, String dataset, ConnectionConfig connection, String taskName) { - super.init(ids, dataset, connection, taskName); - - initWorkers(); - addMetaData(); - generateTripleStats(); - this.rp = new StresstestResultProcessor(this.getMetadata()); - } - - /* - * (non-Javadoc) - * - * @see org.aksw.iguana.cc.tasks.Task#start() - */ - @Override - public void execute() { - this.startDate = GregorianCalendar.getInstance(); - warmup(); - LOGGER.info("Task with ID {{}} will be executed now", this.taskID); - // Execute each Worker in ThreadPool - ExecutorService executor = Executors.newFixedThreadPool(this.workers.size()); - this.startTime = Instant.now(); - for (Worker worker : this.workers) { - executor.execute(worker); - } - LOGGER.info("[TaskID: {{}}]All {{}} workers have been started", this.taskID, this.workers.size()); - // wait timeLimit or noOfQueries - executor.shutdown(); - - // if a time limit is set, let the task thread sleep that amount of time, otherwise sleep for 100 ms - // to periodically check if the workers are finished - long sleep = (timeLimit != null) ? timeLimit.longValue() : 100; - while (!isFinished()) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - for (Worker worker : this.workers) { - sendWorkerResult(worker); - } - - LOGGER.debug("Sending stop signal to workers"); - for (Worker worker : this.workers) { - worker.stopSending(); - } - - // Wait 5seconds so the workers can stop themselves, otherwise they will be - // stopped - try { - LOGGER.debug("Will shutdown now..."); - - LOGGER.info("[TaskID: {{}}] Will shutdown and await termination in 5s.", this.taskID); - boolean finished = executor.awaitTermination(5, TimeUnit.SECONDS); - LOGGER.info("[TaskID: {{}}] Task completed. Thread finished status {}", this.taskID, finished); - } catch (InterruptedException e) { - LOGGER.error("[TaskID: {{}}] Could not shutdown Threads/Workers due to ...", this.taskID); - LOGGER.error("... Exception: ", e); - try { - executor.shutdownNow(); - } catch (Exception e1) { - LOGGER.error("Problems shutting down", e1); - } - } - this.endDate = GregorianCalendar.getInstance(); - } - - private void sendWorkerResult(Worker worker) { - Collection props = worker.popQueryResults(); - if (props == null) { - return; - } - - LOGGER.debug("[TaskID: {{}}] Send results", this.taskID); - this.rp.processQueryExecutions(worker.getMetadata(), props); - LOGGER.debug("[TaskID: {{}}] results could be send", this.taskID); - } - - - @Override - public void close() { - rp.calculateAndSaveMetrics(startDate, endDate); - } - - protected long warmup() { - if (this.warmupTimeMS == null || this.warmupTimeMS == 0L) { - return 0; - } - if (this.warmupWorkers.size() == 0) { - return 0; - } - LOGGER.info("[TaskID: {{}}] will start {{}}ms warmup now using {} no of workers in total.", this.taskID, this.warmupTimeMS, this.warmupWorkers.size()); - return executeWarmup(this.warmupWorkers); - } - - - private long executeWarmup(List warmupWorkers) { - ExecutorService exec = Executors.newFixedThreadPool(2); - for (Worker worker : warmupWorkers) { - exec.submit(worker); - } - //wait as long as needed - Instant start = Instant.now(); - exec.shutdown(); - while (durationInMilliseconds(start, Instant.now()) <= this.warmupTimeMS) { - //clean up RAM - for (Worker worker : warmupWorkers) { - worker.popQueryResults(); - } - try { - TimeUnit.MILLISECONDS.sleep(50); - } catch (Exception e) { - LOGGER.error("Could not warmup "); - } - } - for (Worker worker : warmupWorkers) { - worker.stopSending(); - } - try { - exec.awaitTermination(5, TimeUnit.SECONDS); - - } catch (InterruptedException e) { - LOGGER.warn("[TaskID: {{}}] Warmup. Could not await Termination of Workers.", this.taskID); - } - try { - exec.shutdownNow(); - } catch (Exception e1) { - LOGGER.error("Shutdown problems ", e1); - } - //clear up - long queriesExec = 0; - for (Worker w : warmupWorkers) { - queriesExec += w.getExecutedQueries(); - } - warmupWorkers.clear(); - LOGGER.info("[TaskID: {{}}] Warmup finished.", this.taskID); - return queriesExec; - } - - /** - * Checks if restriction (e.g. timelimit or noOfQueryMixes for each Worker) - * occurs - * - * @return true if restriction occurs, false otherwise - */ - protected boolean isFinished() { - if (this.timeLimit != null) { - - Instant current = Instant.now(); - double passed_time = this.timeLimit - durationInMilliseconds(this.startTime, current); - return passed_time <= 0D; - } else if (this.noOfQueryMixes != null) { - - // use noOfQueries of SPARQLWorkers (as soon as a worker hit the noOfQueries, it - // will stop sending results - // UpdateWorker are allowed to execute all their updates - boolean endFlag = true; - for (Worker worker : this.workers) { - LOGGER.debug("No of query Mixes: {} , queriesInMix {}", this.noOfQueryMixes, worker.getExecutedQueries()); - //Check for each worker, if the - if (worker.hasExecutedNoOfQueryMixes(this.noOfQueryMixes)) { - if (!worker.isTerminated()) { - //if the worker was not already terminated, send last results, as tehy will not be sended afterwards - sendWorkerResult(worker); - } - worker.stopSending(); - } else { - endFlag = false; - } - - } - return endFlag; - } - LOGGER.error("Neither time limit nor NoOfQueryMixes is set. executing task now"); - return true; - } - - public long getExecutedQueries() { - long ret = 0; - for (Worker worker : this.workers) { - ret += worker.getExecutedQueries(); - } - return ret; - } - - public StresstestMetadata getMetadata() { - String classname; - if (this.getClass().isAnnotationPresent(Shorthand.class)) { - classname = this.getClass().getAnnotation(Shorthand.class).value(); - } else { - classname = this.getClass().getCanonicalName(); - } - - Set queryIDs = new HashSet<>(); - WorkerMetadata[] workerMetadata = new WorkerMetadata[this.workers.size()]; - for (int i = 0; i < this.workers.size(); i++) { - workerMetadata[i] = this.workers.get(i).getMetadata(); - queryIDs.addAll(Arrays.asList(workerMetadata[i].queryIDs())); - } - - // TODO: workers might have the same queries, the following code thus adds unnecessary redundancy - // TODO: is sw used for anything? - StringWriter sw = new StringWriter(); - Model tripleStats = ModelFactory.createDefaultModel(); - for (Worker worker : this.workers) { - tripleStats.add(worker.getQueryHandler().getTripleStats(this.taskID)); - } - RDFDataMgr.write(sw, tripleStats, RDFFormat.NTRIPLES); - - // TODO: check for correct values - - return new StresstestMetadata( - suiteID, - expID, - taskID, - datasetID, - conID, - Optional.ofNullable(con.getVersion("")), - taskName, - classname, - Optional.ofNullable(this.timeLimit), - Optional.ofNullable(this.noOfQueryMixes), - workerMetadata, - queryIDs, - Optional.ofNullable(sw.toString()), - Optional.of(tripleStats) - ); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/StresstestMetadata.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/StresstestMetadata.java deleted file mode 100644 index 66233de82..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/StresstestMetadata.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest; - -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.apache.jena.rdf.model.Model; - -import java.util.Optional; -import java.util.Set; - -public record StresstestMetadata( - String suiteID, - String expID, - String taskID, - String datasetID, - String conID, - Optional conVersion, - String taskname, - String classname, - Optional timelimit, - Optional noOfQueryMixes, - WorkerMetadata[] workers, - Set queryIDs, - Optional simpleTriple, - Optional tripleStats -) {} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/StresstestResultProcessor.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/StresstestResultProcessor.java deleted file mode 100644 index c0f2dc790..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/StresstestResultProcessor.java +++ /dev/null @@ -1,230 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.*; -import org.aksw.iguana.cc.tasks.stresstest.storage.StorageManager; -import org.aksw.iguana.commons.rdf.IGUANA_BASE; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.aksw.iguana.commons.rdf.IRES; -import org.apache.jena.rdf.model.*; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; - -import java.util.*; - -public class StresstestResultProcessor { - - private final StresstestMetadata metadata; - private final List metrics; - - /** - * This array contains each query execution, grouped by each worker and each query. - */ - private List[][] workerQueryExecutions; - - /** - * This map contains each query execution, grouped by each query of the task. - */ - private Map> taskQueryExecutions; - - private final Resource taskRes; - - public StresstestResultProcessor(StresstestMetadata metadata) { - this.metadata = metadata; - this.taskRes = IRES.getResource(metadata.taskID()); - this.metrics = MetricManager.getMetrics(); - - WorkerMetadata[] workers = metadata.workers(); - this.workerQueryExecutions = new List[workers.length][]; - for (int i = 0; i < workers.length; i++) { - this.workerQueryExecutions[i] = new List[workers[i].numberOfQueries()]; - for (int j = 0; j < workers[i].numberOfQueries(); j++) { - this.workerQueryExecutions[i][j] = new LinkedList<>(); - } - } - - taskQueryExecutions = new HashMap<>(); - for (String queryID : metadata.queryIDs()) { - taskQueryExecutions.put(queryID, new ArrayList<>()); - } - } - - /** - * This method stores the given query executions statistics from a worker to their appropriate data location. - * - * @param worker the worker that has executed the queries - * @param data a collection of the query execution statistics - */ - public void processQueryExecutions(WorkerMetadata worker, Collection data) { - for(QueryExecutionStats stat : data) { - // The queryIDs returned by the queryHandler are Strings, in the form of ':'. - int queryID = Integer.parseInt(stat.queryID().substring(stat.queryID().indexOf(":") + 1)); - workerQueryExecutions[worker.workerID()][queryID].add(stat); - - taskQueryExecutions.get(stat.queryID()).add(stat); - } - } - - /** - * This method calculates the metrics and creates the RDF model of the result, which will be sent to the storages. - * It uses the given data that was passed with the 'processQueryExecutions' method. - * - * @param start the start date of the task - * @param end the end date of the task - */ - public void calculateAndSaveMetrics(Calendar start, Calendar end) { - Model m = ModelFactory.createDefaultModel(); - Resource suiteRes = IRES.getResource(metadata.suiteID()); - Resource experimentRes = IRES.getResource(metadata.expID()); - Resource datasetRes = IRES.getResource(metadata.datasetID()); - Resource connectionRes = IRES.getResource(metadata.conID()); - - m.add(suiteRes, IPROP.experiment, experimentRes); - m.add(suiteRes, RDF.type, IONT.suite); - m.add(experimentRes, IPROP.dataset, datasetRes); - m.add(experimentRes, RDF.type, IONT.experiment); - m.add(experimentRes, IPROP.task, taskRes); - m.add(datasetRes, RDFS.label, ResourceFactory.createTypedLiteral(metadata.datasetID())); - m.add(datasetRes, RDF.type, IONT.dataset); - m.add(taskRes, IPROP.connection, connectionRes); - if (metadata.noOfQueryMixes().isPresent()) - m.add(taskRes, IPROP.noOfQueryMixes, ResourceFactory.createTypedLiteral(metadata.noOfQueryMixes().get())); - m.add(taskRes, IPROP.noOfWorkers, ResourceFactory.createTypedLiteral(metadata.workers().length)); - if (metadata.timelimit().isPresent()) - m.add(taskRes, IPROP.timeLimit, ResourceFactory.createTypedLiteral(metadata.timelimit().get())); - m.add(taskRes, RDF.type, IONT.task); - - m.add(taskRes, RDF.type, IONT.getClass(metadata.classname())); - if (metadata.conVersion().isPresent()) - m.add(connectionRes, IPROP.version, ResourceFactory.createTypedLiteral(metadata.conVersion().get())); - m.add(connectionRes, RDFS.label, ResourceFactory.createTypedLiteral(metadata.conID())); - m.add(connectionRes, RDF.type, IONT.connection); - - for (WorkerMetadata worker : metadata.workers()) { - Resource workerRes = IRES.getWorkerResource(metadata.taskID(), worker.workerID()); - m.add(taskRes, IPROP.workerResult, workerRes); - m.add(workerRes, IPROP.workerID, ResourceFactory.createTypedLiteral(worker.workerID())); - m.add(workerRes, IPROP.workerType, ResourceFactory.createTypedLiteral(worker.workerType())); - m.add(workerRes, IPROP.noOfQueries, ResourceFactory.createTypedLiteral(worker.queryIDs().length)); - m.add(workerRes, IPROP.timeOut, ResourceFactory.createTypedLiteral(worker.timeout())); - m.add(workerRes, RDF.type, IONT.worker); - } - - if (metadata.tripleStats().isPresent()) { - m.add(metadata.tripleStats().get()); - // Connect task and workers to the Query nodes, that store the triple stats. - for (WorkerMetadata worker : metadata.workers()) { - for (String queryID : worker.queryIDs()) { - int intID = Integer.parseInt(queryID.substring(queryID.indexOf(":") + 1)); - Resource workerQueryRes = IRES.getWorkerQueryResource(metadata.taskID(), worker.workerID(), queryID); - Resource queryRes = IRES.getResource(worker.queryHash() + "/" + intID); - m.add(workerQueryRes, IPROP.queryID, queryRes); - } - - for (String queryID : metadata.queryIDs()) { - int intID = Integer.parseInt(queryID.substring(queryID.indexOf(":") + 1)); - Resource taskQueryRes = IRES.getTaskQueryResource(metadata.taskID(), queryID); - Resource queryRes = IRES.getResource(worker.queryHash() + "/" + intID); - m.add(taskQueryRes, IPROP.queryID, queryRes); - } - } - } - - for (Metric metric : metrics) { - m.add(this.createMetricModel(metric)); - } - - // Task to queries - for (String queryID : metadata.queryIDs()) { - m.add(taskRes, IPROP.query, IRES.getTaskQueryResource(metadata.taskID(), queryID)); - } - - // Worker to queries - for (WorkerMetadata worker : metadata.workers()) { - for (String queryID : worker.queryIDs()) { - Resource workerRes = IRES.getWorkerResource(metadata.taskID(), worker.workerID()); - m.add(workerRes, IPROP.query, IRES.getWorkerQueryResource(metadata.taskID(), worker.workerID(), queryID)); - } - } - - m.add(taskRes, IPROP.startDate, ResourceFactory.createTypedLiteral(start)); - m.add(taskRes, IPROP.endDate, ResourceFactory.createTypedLiteral(end)); - - m.setNsPrefixes(IGUANA_BASE.PREFIX_MAP); - - StorageManager.getInstance().storeResult(m); - } - - /** - * For a given metric this method calculates the metric with the stored data and creates the appropriate - * RDF related to that metric. - * - * @param metric the metric that should be calculated - * @return the result model of the metric - */ - private Model createMetricModel(Metric metric) { - Model m = ModelFactory.createDefaultModel(); - Property metricProp = IPROP.createMetricProperty(metric); - Resource metricRes = IRES.getMetricResource(metric); - - if (metric instanceof ModelWritingMetric) { - m.add(((ModelWritingMetric) metric).createMetricModel(metadata, workerQueryExecutions)); - m.add(((ModelWritingMetric) metric).createMetricModel(metadata, taskQueryExecutions)); - } - - if (metric instanceof TaskMetric) { - Number metricValue = ((TaskMetric) metric).calculateTaskMetric(metadata, workerQueryExecutions); - if (metricValue != null) { - Literal lit = ResourceFactory.createTypedLiteral(metricValue); - m.add(taskRes, metricProp, lit); - } - m.add(taskRes, IPROP.metric, metricRes); - } - - if (metric instanceof WorkerMetric) { - for (WorkerMetadata worker : metadata.workers()) { - Resource workerRes = IRES.getWorkerResource(metadata.taskID(), worker.workerID()); - Number metricValue = ((WorkerMetric) metric).calculateWorkerMetric(worker, workerQueryExecutions[worker.workerID()]); - if (metricValue != null) { - Literal lit = ResourceFactory.createTypedLiteral(metricValue); - m.add(workerRes, metricProp, lit); - } - m.add(workerRes, IPROP.metric, metricRes); - } - } - - if (metric instanceof QueryMetric) { - // queries grouped by worker - for (WorkerMetadata worker : metadata.workers()) { - for (int i = 0; i < worker.numberOfQueries(); i++) { - Number metricValue = ((QueryMetric) metric).calculateQueryMetric(workerQueryExecutions[worker.workerID()][i]); - if (metricValue != null) { - Literal lit = ResourceFactory.createTypedLiteral(metricValue); - Resource queryRes = IRES.getWorkerQueryResource(metadata.taskID(), worker.workerID(), worker.queryIDs()[i]); - m.add(queryRes, metricProp, lit); - } - } - } - - // queries grouped by task - for (String queryID : taskQueryExecutions.keySet()) { - Number metricValue = ((QueryMetric) metric).calculateQueryMetric(taskQueryExecutions.get(queryID)); - if (metricValue != null) { - Literal lit = ResourceFactory.createTypedLiteral(metricValue); - Resource queryRes = IRES.getTaskQueryResource(metadata.taskID(), queryID); - m.add(queryRes, metricProp, lit); - } - } - } - - m.add(metricRes, RDFS.label, metric.getName()); - m.add(metricRes, RDFS.label, metric.getAbbreviation()); - m.add(metricRes, RDFS.comment, metric.getDescription()); - m.add(metricRes, RDF.type, IONT.getMetricClass(metric)); - m.add(metricRes, RDF.type, IONT.metric); - - return m; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/Metric.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/Metric.java deleted file mode 100644 index 2e6a79403..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/Metric.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics; - -public abstract class Metric { - private final String name; - private final String abbreviation; - private final String description; - - public Metric(String name, String abbreviation, String description) { - this.name = name; - this.abbreviation = abbreviation; - this.description = description; - } - - - public String getDescription(){ - return this.description; - } - - public String getName(){ - return this.name; - } - - - public String getAbbreviation(){ - return this.abbreviation; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/MetricManager.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/MetricManager.java deleted file mode 100644 index 5320774fa..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/MetricManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics; - -import java.util.List; - -public class MetricManager { - private static List metrics; - - public static void setMetrics(List metrics) { - MetricManager.metrics = metrics; - } - - public static List getMetrics() { - return MetricManager.metrics; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/ModelWritingMetric.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/ModelWritingMetric.java deleted file mode 100644 index bbce4aa49..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/ModelWritingMetric.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; - -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Map; - -public interface ModelWritingMetric { - default @Nonnull Model createMetricModel(StresstestMetadata task, List[][] data) { - return ModelFactory.createDefaultModel(); - } - - default @Nonnull Model createMetricModel(StresstestMetadata task, Map> data) { - return ModelFactory.createDefaultModel(); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/QueryMetric.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/QueryMetric.java deleted file mode 100644 index f4a793f1a..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/QueryMetric.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics; - -import org.aksw.iguana.cc.model.QueryExecutionStats; - -import java.util.List; - -public interface QueryMetric { - Number calculateQueryMetric(List data); -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/TaskMetric.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/TaskMetric.java deleted file mode 100644 index 6995f24d1..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/TaskMetric.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; - -import java.util.List; - -public interface TaskMetric { - Number calculateTaskMetric(StresstestMetadata task, List[][] data); -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/WorkerMetric.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/WorkerMetric.java deleted file mode 100644 index bc81071e6..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/WorkerMetric.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.worker.WorkerMetadata; - -import java.util.List; - -public interface WorkerMetric { - Number calculateWorkerMetric(WorkerMetadata worker, List[] data); -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/AggregatedExecutionStatistics.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/AggregatedExecutionStatistics.java deleted file mode 100644 index a2e332cba..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/AggregatedExecutionStatistics.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.ModelWritingMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.aksw.iguana.commons.rdf.IRES; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.vocabulary.RDF; - -import javax.annotation.Nonnull; -import java.math.BigInteger; -import java.time.Duration; -import java.util.List; -import java.util.Map; - -import static org.aksw.iguana.commons.time.TimeUtils.toXSDDurationInSeconds; - -@Shorthand("AES") -public class AggregatedExecutionStatistics extends Metric implements ModelWritingMetric { - - public AggregatedExecutionStatistics() { - super("Aggregated Execution Statistics", "AES", "Sums up the statistics of each query execution for each query a worker and task has. The result size only contains the value of the last execution."); - } - - @Override - @Nonnull - public Model createMetricModel(StresstestMetadata task, List[][] data) { - Model m = ModelFactory.createDefaultModel(); - for (WorkerMetadata worker : task.workers()) { - for (int i = 0; i < worker.numberOfQueries(); i++) { - Resource queryRes = IRES.getWorkerQueryResource(task.taskID(), worker.workerID(), worker.queryIDs()[i]); - m.add(createAggregatedModel(data[worker.workerID()][i], queryRes)); - } - } - return m; - } - - @Override - @Nonnull - public Model createMetricModel(StresstestMetadata task, Map> data) { - Model m = ModelFactory.createDefaultModel(); - for (String queryID : data.keySet()) { - Resource queryRes = IRES.getTaskQueryResource(task.taskID(), queryID); - m.add(createAggregatedModel(data.get(queryID), queryRes)); - } - return m; - } - - private static Model createAggregatedModel(List data, Resource queryRes) { - Model m = ModelFactory.createDefaultModel(); - BigInteger succeeded = BigInteger.ZERO; - BigInteger failed = BigInteger.ZERO; - BigInteger resultSize = BigInteger.ZERO; - BigInteger wrongCodes = BigInteger.ZERO; - BigInteger timeOuts = BigInteger.ZERO; - BigInteger unknownExceptions = BigInteger.ZERO; - Duration totalTime = Duration.ZERO; - - for (QueryExecutionStats exec : data) { - // TODO: make response code integer - switch ((int) exec.responseCode()) { - case (int) COMMON.QUERY_SUCCESS -> succeeded = succeeded.add(BigInteger.ONE); - case (int) COMMON.QUERY_SOCKET_TIMEOUT -> { - timeOuts = timeOuts.add(BigInteger.ONE); - failed = failed.add(BigInteger.ONE); - } - case (int) COMMON.QUERY_HTTP_FAILURE -> { - wrongCodes = wrongCodes.add(BigInteger.ONE); - failed = failed.add(BigInteger.ONE); - } - case (int) COMMON.QUERY_UNKNOWN_EXCEPTION -> { - unknownExceptions = unknownExceptions.add(BigInteger.ONE); - failed = failed.add(BigInteger.ONE); - } - } - - totalTime = totalTime.plusNanos((long) (exec.executionTime() * 1000000)); - resultSize = BigInteger.valueOf(exec.resultSize()); - } - - m.add(queryRes, IPROP.succeeded, ResourceFactory.createTypedLiteral(succeeded)); - m.add(queryRes, IPROP.failed, ResourceFactory.createTypedLiteral(failed)); - m.add(queryRes, IPROP.resultSize, ResourceFactory.createTypedLiteral(resultSize)); - m.add(queryRes, IPROP.timeOuts, ResourceFactory.createTypedLiteral(timeOuts)); - m.add(queryRes, IPROP.wrongCodes, ResourceFactory.createTypedLiteral(wrongCodes)); - m.add(queryRes, IPROP.unknownException, ResourceFactory.createTypedLiteral(unknownExceptions)); - m.add(queryRes, IPROP.totalTime, ResourceFactory.createTypedLiteral(toXSDDurationInSeconds(totalTime))); - m.add(queryRes, RDF.type, IONT.executedQuery); - - return m; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/AvgQPS.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/AvgQPS.java deleted file mode 100644 index 5d56e7f9e..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/AvgQPS.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.TaskMetric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.WorkerMetric; -import org.aksw.iguana.commons.annotation.Shorthand; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.List; - -@Shorthand("AvgQPS") -public class AvgQPS extends Metric implements TaskMetric, WorkerMetric { - - public AvgQPS() { - super("Average Queries per Second", "AvgQPS", "This metric calculates the average QPS between all queries."); - } - - @Override - public Number calculateTaskMetric(StresstestMetadata task, List[][] data) { - BigDecimal sum = BigDecimal.ZERO; - for (WorkerMetadata worker : task.workers()) { - sum = sum.add((BigDecimal) this.calculateWorkerMetric(worker, data[worker.workerID()])); - } - - try { - return sum.divide(BigDecimal.valueOf(data.length), 10, RoundingMode.HALF_UP); - } catch (ArithmeticException e) { - return BigDecimal.ZERO; - } - } - - @Override - public Number calculateWorkerMetric(WorkerMetadata worker, List[] data) { - BigDecimal sum = BigDecimal.ZERO; - QPS qpsmetric = new QPS(); - for (List datum : data) { - sum = sum.add((BigDecimal) qpsmetric.calculateQueryMetric(datum)); - } - - try { - return sum.divide(BigDecimal.valueOf(data.length), 10, RoundingMode.HALF_UP); - } catch (ArithmeticException e) { - return BigDecimal.ZERO; - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/EachExecutionStatistic.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/EachExecutionStatistic.java deleted file mode 100644 index f3771943a..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/EachExecutionStatistic.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.ModelWritingMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.aksw.iguana.commons.rdf.IPROP; -import org.aksw.iguana.commons.rdf.IRES; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.ResourceFactory; - -import javax.annotation.Nonnull; -import java.math.BigInteger; -import java.util.List; - -@Shorthand("EachQuery") -public class EachExecutionStatistic extends Metric implements ModelWritingMetric { - - public EachExecutionStatistic() { - super("Each Query Execution Statistic", "EachQuery", "This metric saves the statistics of each query execution."); - } - - @Override - @Nonnull - public Model createMetricModel(StresstestMetadata task, List[][] data) { - Model m = ModelFactory.createDefaultModel(); - for (WorkerMetadata worker : task.workers()) { - for (int i = 0; i < worker.numberOfQueries(); i++) { - Resource queryRes = IRES.getWorkerQueryResource(task.taskID(), worker.workerID(), worker.queryIDs()[i]); - Resource query = IRES.getResource(worker.queryHash() + "/" + worker.queryIDs()[i]); - BigInteger run = BigInteger.ONE; - for (QueryExecutionStats exec : data[worker.workerID()][i]) { - Resource runRes = IRES.getWorkerQueryRunResource(task.taskID(), worker.workerID(), worker.queryIDs()[i], run); - m.add(queryRes, IPROP.queryExecution, runRes); - m.add(runRes, IPROP.time, ResourceFactory.createTypedLiteral(exec.executionTime())); - m.add(runRes, IPROP.success, ResourceFactory.createTypedLiteral(exec.responseCode() == COMMON.QUERY_SUCCESS)); - m.add(runRes, IPROP.run, ResourceFactory.createTypedLiteral(run)); - m.add(runRes, IPROP.code, ResourceFactory.createTypedLiteral(exec.responseCode())); - m.add(runRes, IPROP.resultSize, ResourceFactory.createTypedLiteral(exec.resultSize())); - m.add(runRes, IPROP.queryID, query); - run = run.add(BigInteger.ONE); - } - } - } - return m; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/NoQ.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/NoQ.java deleted file mode 100644 index e1af28be1..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/NoQ.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.TaskMetric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.WorkerMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; - -import java.math.BigInteger; -import java.util.List; - -@Shorthand("NoQ") -public class NoQ extends Metric implements TaskMetric, WorkerMetric { - - public NoQ() { - super("Number of Queries", "NoQ", "This metric calculates the number of successfully executed queries."); - } - - @Override - public Number calculateTaskMetric(StresstestMetadata task, List[][] data) { - BigInteger sum = BigInteger.ZERO; - for (WorkerMetadata worker : task.workers()) { - sum = sum.add((BigInteger) this.calculateWorkerMetric(worker, data[worker.workerID()])); - } - return sum; - } - - @Override - public Number calculateWorkerMetric(WorkerMetadata worker, List[] data) { - BigInteger sum = BigInteger.ZERO; - for (List datum : data) { - for (QueryExecutionStats exec : datum) { - if (exec.responseCode() == COMMON.QUERY_SUCCESS) { - sum = sum.add(BigInteger.ONE); - } - } - } - return sum; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/NoQPH.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/NoQPH.java deleted file mode 100644 index 4015d3df9..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/NoQPH.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.TaskMetric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.WorkerMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.time.Duration; -import java.util.List; - -@Shorthand("NoQPH") -public class NoQPH extends Metric implements TaskMetric, WorkerMetric { - - public NoQPH() { - super("Number of Queries per Hour", "NoQPH", "This metric calculates the number of successfully executed queries per hour."); - } - @Override - public Number calculateTaskMetric(StresstestMetadata task, List[][] data) { - BigDecimal sum = BigDecimal.ZERO; - for (WorkerMetadata worker : task.workers()) { - sum = sum.add((BigDecimal) this.calculateWorkerMetric(worker, data[worker.workerID()])); - } - return sum; - } - - @Override - public Number calculateWorkerMetric(WorkerMetadata worker, List[] data) { - BigDecimal successes = BigDecimal.ZERO; - Duration totalTime = Duration.ZERO; - for (List datum : data) { - for (QueryExecutionStats exec : datum) { - if (exec.responseCode() == COMMON.QUERY_SUCCESS) { - successes = successes.add(BigDecimal.ONE); - totalTime = totalTime.plusNanos((long) exec.executionTime() * 1000000); - } - } - } - BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)).divide(BigDecimal.valueOf(3600), 20, RoundingMode.HALF_UP); - - try { - return successes.divide(tt, 10, RoundingMode.HALF_UP); - } catch (ArithmeticException e) { - return BigDecimal.ZERO; - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/PQPS.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/PQPS.java deleted file mode 100644 index bbefdc12b..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/PQPS.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.QueryMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.time.Duration; -import java.util.List; - -@Shorthand("PQPS") -public class PQPS extends Metric implements QueryMetric { - - private final int penalty; - - public PQPS(Integer penalty) { - super("Penalized Queries per Second", "PQPS", "This metric calculates for each query the amount of executions per second. Failed executions receive a time penalty."); - this.penalty = penalty; - } - - @Override - public Number calculateQueryMetric(List data) { - BigDecimal successes = BigDecimal.ZERO; - Duration totalTime = Duration.ZERO; - for (QueryExecutionStats exec : data) { - successes = successes.add(BigDecimal.ONE); - if (exec.responseCode() == COMMON.QUERY_SUCCESS) { - totalTime = totalTime.plusNanos((long) exec.executionTime() * 1000000); - } else { - totalTime = totalTime.plusMillis(penalty); - } - } - BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)); - - try { - return successes.divide(tt, 10, RoundingMode.HALF_UP); - } catch (ArithmeticException e) { - return BigDecimal.ZERO; - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/QMPH.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/QMPH.java deleted file mode 100644 index c3a3e01cf..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/metrics/impl/QMPH.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.metrics.impl; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.tasks.stresstest.StresstestMetadata; -import org.aksw.iguana.cc.worker.WorkerMetadata; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.TaskMetric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.WorkerMetric; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.time.Duration; -import java.util.List; - -@Shorthand("QMPH") -public class QMPH extends Metric implements TaskMetric, WorkerMetric { - - public QMPH() { - super("Query Mixes per Hour", "QMPH", "This metric calculates the amount of query mixes (a given set of queries) that are executed per hour."); - } - @Override - public Number calculateTaskMetric(StresstestMetadata task, List[][] data) { - BigDecimal sum = BigDecimal.ZERO; - for (WorkerMetadata worker : task.workers()) { - sum = sum.add((BigDecimal) this.calculateWorkerMetric(worker, data[worker.workerID()])); - } - return sum; - } - - @Override - public Number calculateWorkerMetric(WorkerMetadata worker, List[] data) { - BigDecimal successes = BigDecimal.ZERO; - BigDecimal noq = BigDecimal.valueOf(worker.numberOfQueries()); - Duration totalTime = Duration.ZERO; - for (List datum : data) { - for (QueryExecutionStats exec : datum) { - if (exec.responseCode() == COMMON.QUERY_SUCCESS) { - successes = successes.add(BigDecimal.ONE); - totalTime = totalTime.plusNanos((long) exec.executionTime() * 1000000); - } - } - } - BigDecimal tt = (new BigDecimal(BigInteger.valueOf(totalTime.toNanos()), 9)).divide(BigDecimal.valueOf(3600), 20, RoundingMode.HALF_UP); - - try { - return successes.divide(tt, 10, RoundingMode.HALF_UP).divide(noq, 10, RoundingMode.HALF_UP); - } catch (ArithmeticException e) { - return BigDecimal.ZERO; - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/Storage.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/Storage.java deleted file mode 100644 index d1b045651..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/Storage.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.storage; - -import org.apache.jena.rdf.model.Model; - -/** - * Interface for the Result Storages - * - * @author f.conrads - * - */ -public interface Storage { - - /** - * Stores the task result into the storage. This method will be executed after a task has finished. - * - * @param data the given result model - */ - void storeResult(Model data); -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/StorageManager.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/StorageManager.java deleted file mode 100644 index 5de7fd4e0..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/StorageManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.storage; - -import org.apache.jena.rdf.model.Model; - -import java.util.*; - - -/** - * Manager for Storages - * - * @author f.conrads - * - */ -public class StorageManager { - - private Set storages = new HashSet<>(); - - private static StorageManager instance; - - public static synchronized StorageManager getInstance() { - if (instance == null) { - instance = new StorageManager(); - } - return instance; - } - - /** - * Will add the Storage - * - * @param storage - */ - public void addStorage(Storage storage){ - if(storage==null){ - return; - } - storages.add(storage); - } - - /** - * Will return each Storage - * - * @return - */ - public Set getStorages(){ - return storages; - } - - /** - * Simply adds a Model - * @param m - */ - public void storeResult(Model m){ - for(Storage s : storages){ - s.storeResult(m); - } - } - - @Override - public String toString(){ - StringBuilder ret = new StringBuilder(); - Iterator it = storages.iterator(); - for(int i=0;i storages) { - this.storages.addAll(storages); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/TripleBasedStorage.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/TripleBasedStorage.java deleted file mode 100644 index 599113307..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/TripleBasedStorage.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks.stresstest.storage; - -import org.aksw.iguana.commons.constants.COMMON; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; - -/** - * This Storage will save all the metric results as triples - * - * @author f.conrads - * - */ -public abstract class TripleBasedStorage implements Storage { - - protected String baseUri = COMMON.BASE_URI; - protected Model metricResults = ModelFactory.createDefaultModel(); - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - - public void storeResult(Model data){ - metricResults.add(data); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/CSVStorage.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/CSVStorage.java deleted file mode 100644 index f382c8e45..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/CSVStorage.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.storage.impl; - -import com.opencsv.CSVReader; -import com.opencsv.CSVWriter; -import com.opencsv.CSVWriterBuilder; -import com.opencsv.exceptions.CsvValidationException; -import org.aksw.iguana.cc.config.IguanaConfig; -import org.aksw.iguana.cc.tasks.stresstest.metrics.*; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.AggregatedExecutionStatistics; -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.apache.jena.arq.querybuilder.SelectBuilder; -import org.apache.jena.query.*; -import org.apache.jena.rdf.model.*; -import org.apache.jena.sparql.lang.sparql_11.ParseException; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.function.Predicate; - -@Shorthand("CSVStorage") -public class CSVStorage implements Storage { - - private static final Logger LOGGER = LoggerFactory.getLogger(CSVStorage.class); - - private final Path folder; - private final Path taskFile; - - private List workerResources; - private Resource taskRes; - private String connection; - private String connectionVersion; - private String dataset; - - public CSVStorage(String folderPath) { - Path parentFolder; - try { - parentFolder = Paths.get(folderPath); - } catch (InvalidPathException e) { - LOGGER.error("Can't store csv files, the given path is invalid.", e); - this.folder = null; - this.taskFile = null; - return; - } - - this.folder = parentFolder.resolve(IguanaConfig.getSuiteID()); - this.taskFile = this.folder.resolve("tasks-overview.csv"); - - if (Files.notExists(parentFolder)) { - try { - Files.createDirectory(parentFolder); - } catch (IOException e) { - LOGGER.error("Can't store csv files, directory couldn't be created.", e); - return; - } - } - - if (Files.notExists(folder)) { - try { - Files.createDirectory(folder); - } catch (IOException e) { - LOGGER.error("Can't store csv files, directory couldn't be created.", e); - return; - } - } - - try { - Files.createFile(taskFile); - } catch (IOException e) { - LOGGER.error("Couldn't create the file: " + taskFile.toAbsolutePath(), e); - return; - } - - // write headers for the tasks.csv file - // This only works because the metrics are initialized sooner - try (CSVWriter csvWriter = getCSVWriter(taskFile)) { - Metric[] taskMetrics = MetricManager.getMetrics().stream().filter(x -> TaskMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); - List headerList = new LinkedList<>(); - headerList.addAll(List.of("connection", "dataset", "startDate", "endDate", "noOfWorkers")); - headerList.addAll(Arrays.stream(taskMetrics).map(Metric::getAbbreviation).toList()); - String[] header = headerList.toArray(String[]::new); - csvWriter.writeNext(header, true); - } catch (IOException e) { - LOGGER.error("Error while writing to file: " + taskFile.toAbsolutePath(), e); - } - } - - /** - * Stores the task result into the storage. This method will be executed after a task has finished. - * - * @param data the given result model - */ - @Override - public void storeResult(Model data) { - try { - setObjectAttributes(data); - } catch (NoSuchElementException e) { - LOGGER.error("Error while querying the result model. The given model is probably incorrect.", e); - return; - } - - try { - storeTaskResults(data); - } catch (IOException e) { - LOGGER.error("Error while storing the task result in a csv file.", e); - } catch (NoSuchElementException | ParseException e) { - LOGGER.error("Error while storing the task result in a csv file. The given model is probably incorrect.", e); - } - - try { - Path temp = createCSVFile(dataset, connection, connectionVersion, "worker"); - storeWorkerResults(this.taskRes, temp, data); - for (Resource workerRes : workerResources) { - String workerID = data.listObjectsOfProperty(workerRes, IPROP.workerID).next().asLiteral().getLexicalForm(); - try { - Path file = createCSVFile(dataset, connection, connectionVersion, "worker", "query", workerID); - storeQueryResults(workerRes, file, data); - } catch (IOException e) { - LOGGER.error("Error while storing the query results of a worker in a csv file.", e); - } catch (NoSuchElementException e) { - LOGGER.error("Error while storing the query results of a worker in a csv file. The given model is probably incorrect.", e); - } - } - } catch (IOException e) { - LOGGER.error("Error while storing the worker results in a csv file.", e); - } catch (NoSuchElementException e) { - LOGGER.error("Error while storing the worker results in a csv file. The given model is probably incorrect.", e); - } - - try { - Path file = createCSVFile(dataset, connection, connectionVersion, "query"); - storeQueryResults(taskRes, file, data); - } catch (IOException e) { - LOGGER.error("Error while storing the query results of a task result in a csv file.", e); - } catch (NoSuchElementException e) { - LOGGER.error("Error while storing the query results of a task result in a csv file. The given model is probably incorrect.", e); - } - } - - /** - * This method sets the objects attributes by querying the given model. - * - * @param data the result model - * @throws NoSuchElementException might be thrown if the model is incorrect - */ - private void setObjectAttributes(Model data) throws NoSuchElementException { - ResIterator resIterator = data.listSubjectsWithProperty(RDF.type, IONT.dataset); - Resource datasetRes = resIterator.nextResource(); - NodeIterator nodeIterator = data.listObjectsOfProperty(datasetRes, RDFS.label); - this.dataset = nodeIterator.next().asLiteral().getLexicalForm(); - - resIterator = data.listSubjectsWithProperty(RDF.type, IONT.connection); - Resource connectionRes = resIterator.nextResource(); - nodeIterator = data.listObjectsOfProperty(connectionRes, RDFS.label); - this.connection = nodeIterator.next().asLiteral().getLexicalForm(); - this.connectionVersion = ""; - nodeIterator = data.listObjectsOfProperty(connectionRes, IPROP.version); - if (nodeIterator.hasNext()) { - this.connectionVersion = nodeIterator.next().toString(); - } - - resIterator = data.listSubjectsWithProperty(RDF.type, IONT.task); - this.taskRes = resIterator.nextResource(); - - nodeIterator = data.listObjectsOfProperty(this.taskRes, IPROP.workerResult); - this.workerResources = nodeIterator.toList().stream().map(RDFNode::asResource).toList(); - } - - /** - * Creates a CSV file with the given name values that will be located inside the parent folder. The name value are - * joined together with the character '-'. Empty values will be ignored. - * - * @param nameValues strings that build up the name of the file - * @throws IOException if an I/O error occurs - * @return path object to the created CSV file - */ - private Path createCSVFile(String... nameValues) throws IOException { - // remove empty string values - nameValues = Arrays.stream(nameValues).filter(Predicate.not(String::isEmpty)).toArray(String[]::new); - String filename = String.join("-", nameValues) + ".csv"; - Path file = this.folder.resolve(filename); - Files.createFile(file); - return file; - } - - private static void storeQueryResults(Resource parentRes, Path file, Model data) throws IOException, NoSuchElementException { - boolean containsAggrStats = !MetricManager.getMetrics().stream().filter(AggregatedExecutionStatistics.class::isInstance).toList().isEmpty(); - Metric[] queryMetrics = MetricManager.getMetrics().stream().filter(x -> QueryMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); - - SelectBuilder sb = new SelectBuilder(); - sb.addWhere(parentRes, IPROP.query, "?eQ"); - queryProperties(sb, "?eQ", IPROP.queryID); - if (containsAggrStats) { - queryProperties(sb, "?eQ", IPROP.succeeded, IPROP.failed, IPROP.totalTime, IPROP.resultSize, IPROP.wrongCodes, IPROP.timeOuts, IPROP.unknownException); - } - queryMetrics(sb, "?eQ", queryMetrics); - - executeAndStoreQuery(sb, file, data); - } - - private void storeTaskResults(Model data) throws IOException, NoSuchElementException, ParseException { - Metric[] taskMetrics = MetricManager.getMetrics().stream().filter(x -> TaskMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); - - SelectBuilder sb = new SelectBuilder(); - sb.addVar("connection") - .addWhere("?taskRes", IPROP.connection, "?connRes") - .addWhere("?connRes", RDFS.label, "?connection") - .addVar("dataset") - .addWhere("?expRes", IPROP.dataset, "?datasetRes") - .addWhere("?datasetRes", RDFS.label, "?dataset"); - queryProperties(sb, String.format("<%s>", this.taskRes.toString()), IPROP.startDate, IPROP.endDate, IPROP.noOfWorkers); - queryMetrics(sb, String.format("<%s>", this.taskRes.toString()), taskMetrics); - - try(QueryExecution exec = QueryExecutionFactory.create(sb.build(), data); - CSVWriter csvWriter = getCSVWriter(taskFile); - ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - ResultSet results = exec.execSelect(); - ResultSetFormatter.outputAsCSV(baos, results); - - // workaround to remove the created header from the ResultSetFormatter - CSVReader reader = new CSVReader(new StringReader(baos.toString())); - try { - reader.readNext(); - csvWriter.writeNext(reader.readNext(), true); - } catch (CsvValidationException ignored) { - // shouldn't happen - } - } - } - - private static void storeWorkerResults(Resource taskRes, Path file, Model data) throws IOException, NoSuchElementException { - Metric[] workerMetrics = MetricManager.getMetrics().stream().filter(x -> WorkerMetric.class.isAssignableFrom(x.getClass())).toArray(Metric[]::new); - - SelectBuilder sb = new SelectBuilder(); - sb.addWhere(taskRes, IPROP.workerResult, "?worker"); - queryProperties(sb, "?worker", IPROP.workerID, IPROP.workerType, IPROP.noOfQueries, IPROP.timeOut); - queryMetrics(sb, "?worker", workerMetrics); - - executeAndStoreQuery(sb, file, data); - } - - private static CSVWriter getCSVWriter(Path file) throws IOException { - return (CSVWriter) new CSVWriterBuilder(new FileWriter(file.toAbsolutePath().toString(), true)) - .withQuoteChar('\"') - .withSeparator(',') - .withLineEnd("\n") - .build(); - } - - private static void queryProperties(SelectBuilder sb, String variable, Property... properties) { - for (Property prop : properties) { - sb.addVar(prop.getLocalName()).addWhere(variable, prop, "?" + prop.getLocalName()); - } - } - - private static void queryMetrics(SelectBuilder sb, String variable, Metric[] metrics) { - for (Metric m : metrics) { - sb.addVar(m.getAbbreviation()).addWhere(variable, IPROP.createMetricProperty(m), "?" + m.getAbbreviation()); - } - } - - private static void executeAndStoreQuery(SelectBuilder sb, Path file, Model data) throws IOException { - try(QueryExecution exec = QueryExecutionFactory.create(sb.build(), data); - FileOutputStream fos = new FileOutputStream(file.toFile())) { - ResultSet results = exec.execSelect(); - ResultSetFormatter.outputAsCSV(fos, results); - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/NTFileStorage.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/NTFileStorage.java deleted file mode 100644 index cd94e3e15..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/NTFileStorage.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks.stresstest.storage.impl; - -import org.aksw.iguana.cc.tasks.stresstest.storage.TripleBasedStorage; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.riot.RDFDataMgr; -import org.apache.jena.riot.RDFFormat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Calendar; - -/** - * - * Will save results as NTriple File either using the provided name or the a generated one. - * - * @author f.conrads - * - */ -@Shorthand("NTFileStorage") -public class NTFileStorage extends TripleBasedStorage { - - private static final Logger LOGGER = LoggerFactory.getLogger(NTFileStorage.class); - - private final StringBuilder file; - - /** - * Uses a generated file called results_{DD}-{MM}-{YYYY}_{HH}-{mm}.nt - */ - public NTFileStorage() { - Calendar now = Calendar.getInstance(); - - this.file = new StringBuilder(); - file.append("results_") - .append( - String.format("%d-%02d-%02d_%02d-%02d.%03d", - now.get(Calendar.YEAR), - now.get(Calendar.MONTH) + 1, - now.get(Calendar.DAY_OF_MONTH), - now.get(Calendar.HOUR_OF_DAY), - now.get(Calendar.MINUTE), - now.get(Calendar.MILLISECOND) - ) - ) - .append(".nt"); - } - - /** - * Uses the provided filename - * - * @param fileName - */ - public NTFileStorage(String fileName) { - this.file = new StringBuilder(fileName); - } - - @Override - public void storeResult(Model data) { - super.storeResult(data); - try (OutputStream os = new FileOutputStream(file.toString(), true)) { - RDFDataMgr.write(os, metricResults, RDFFormat.NTRIPLES); - metricResults.removeAll(); - } catch (IOException e) { - LOGGER.error("Could not commit to NTFileStorage.", e); - } - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - - public String getFileName() { - return this.file.toString(); - } -} - diff --git a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/RDFFileStorage.java b/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/RDFFileStorage.java deleted file mode 100644 index 10ee67817..000000000 --- a/src/main/java/org/aksw/iguana/cc/tasks/stresstest/storage/impl/RDFFileStorage.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest.storage.impl; - -import org.aksw.iguana.cc.tasks.stresstest.storage.TripleBasedStorage; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFDataMgr; -import org.apache.jena.riot.RDFLanguages; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Calendar; - -@Shorthand("RDFFileStorage") -public class RDFFileStorage extends TripleBasedStorage { - - private static final Logger LOGGER = LoggerFactory.getLogger(RDFFileStorage.class.getName()); - - private Lang lang = Lang.TTL; - private final StringBuilder file; - - /** - * Uses a generated file called results_{DD}-{MM}-{YYYY}_{HH}-{mm}.ttl - */ - public RDFFileStorage() { - Calendar now = Calendar.getInstance(); - - this.file = new StringBuilder(); - file.append("results_") - .append( - String.format("%d-%02d-%02d_%02d-%02d.%03d", - now.get(Calendar.YEAR), - now.get(Calendar.MONTH) + 1, - now.get(Calendar.DAY_OF_MONTH), - now.get(Calendar.HOUR_OF_DAY), - now.get(Calendar.MINUTE), - now.get(Calendar.MILLISECOND) - ) - ) - .append(".ttl"); - } - - /** - * Uses the provided filename - * @param fileName - */ - public RDFFileStorage(String fileName){ - this.file = new StringBuilder(fileName); - this.lang= RDFLanguages.filenameToLang(fileName, Lang.TTL); - } - - @Override - public void storeResult(Model data){ - super.storeResult(data); - try (OutputStream os = new FileOutputStream(file.toString(), true)) { - RDFDataMgr.write(os, metricResults, this.lang); - metricResults.removeAll(); - } catch (IOException e) { - LOGGER.error("Could not commit to RDFFileStorage using lang: "+lang, e); - } - } - - - @Override - public String toString(){ - return this.getClass().getSimpleName(); - } - - public String getFileName(){ - return this.file.toString(); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/utils/CLIProcessManager.java b/src/main/java/org/aksw/iguana/cc/utils/CLIProcessManager.java deleted file mode 100644 index 42a8bdd61..000000000 --- a/src/main/java/org/aksw/iguana/cc/utils/CLIProcessManager.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.apache.commons.lang.SystemUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.util.ArrayList; -import java.util.List; - -/** - * CLI Utils class - */ -public class CLIProcessManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(CLIProcessManager.class); - - /** - * Creates a process - * @param command - * @return - */ - public static Process createProcess(String command) { - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.redirectErrorStream(true); - - Process process = null; - try { - if (SystemUtils.IS_OS_LINUX) { - - processBuilder.command("bash", "-c", command); - - } else if (SystemUtils.IS_OS_WINDOWS) { - processBuilder.command("cmd.exe", "-c", command); - } - process = processBuilder.start(); - - } catch (IOException e) { - LOGGER.error("New process could not be created: {}", e); - } - - return process; - } - - /** - * Destroys a process forcibly - * @param process - */ - public static void destroyProcess(Process process) { - process.destroyForcibly(); - } - - /** - * Short handler for destroyProcess and createProcess - * @param process - * @param command - * @return - */ - public static Process destroyAndCreateNewProcess(Process process, String command) { - destroyProcess(process); - return createProcess(command); - } - - /** - * Create n processes of the same command - * @param n the amount of processes created - * @param command the command to create the process with - * @return - */ - public static List createProcesses(int n, String command) { - List processList = new ArrayList<>(5); - for (int i = 0; i < n; i++) { - processList.add(createProcess(command)); - } - - return processList; - } - - /** - * Count and returns the no. of lines of one process until a certain string appears, - * @param process - * @param successString the string of the process after the no of line should be returned - * @param errorString the error string, will throw an IOException if this appeared. - * @return - * @throws IOException - */ - public static long countLinesUntilStringOccurs(Process process, String successString, String errorString) throws IOException { - String line; - LOGGER.debug("Will look for: {} or as error: {}",successString, errorString); - StringBuilder output = new StringBuilder(); - - long size = -1; - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - - try { - while ((line = reader.readLine()) != null) { - if (line.contains(errorString)) { - LOGGER.debug("Found error"); - LOGGER.debug("Query finished with {}", errorString); - - throw new IOException(line); - } else if (line.contains(successString)) { - LOGGER.debug("Query finished with {}", successString); - break; - } - - // Only save first 1000 lines of the output - if (size < 1000) { - output.append(line).append("\n"); - } - size++; - } - - } catch (IOException e) { - LOGGER.debug("Exception in reading the output of the process. ", e); - throw e; - } - - return size; - } - - public static void executeCommand(Process process, String command) throws IOException { - BufferedWriter output = new BufferedWriter(new OutputStreamWriter(process.getOutputStream())); - output.write(command + "\n"); - output.flush(); - } - - /** - * Checks if the process input stream is ready to be read. - * @param process - * @return - * @throws IOException - */ - public static boolean isReaderReady(Process process) throws IOException { - return new BufferedReader(new InputStreamReader(process.getInputStream())).ready(); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/utils/FileUtils.java b/src/main/java/org/aksw/iguana/cc/utils/FileUtils.java index 0cdaec81c..745150f12 100644 --- a/src/main/java/org/aksw/iguana/cc/utils/FileUtils.java +++ b/src/main/java/org/aksw/iguana/cc/utils/FileUtils.java @@ -3,7 +3,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -15,7 +15,7 @@ */ public class FileUtils { - public static int getHashcodeFromFileContent(String filepath) { + public static int getHashcodeFromFileContent(Path filepath) { int hashcode; try { String fileContents = readFile(filepath); @@ -26,9 +26,8 @@ public static int getHashcodeFromFileContent(String filepath) { return hashcode; } - public static String readFile(String path) throws IOException { - byte[] encoded = Files.readAllBytes(Paths.get(path)); - return new String(encoded, StandardCharsets.UTF_8); + public static String readFile(Path path) throws IOException { + return Files.readString(path, StandardCharsets.UTF_8); } /** @@ -46,10 +45,8 @@ public static String readFile(String path) throws IOException { * @return the line ending used in the given file * @throws IOException */ - public static String getLineEnding(String filepath) throws IOException { - try(FileInputStream fis = new FileInputStream(filepath); - InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); - BufferedReader br = new BufferedReader(isr)) { + public static String getLineEnding(Path filepath) throws IOException { + try(BufferedReader br = Files.newBufferedReader(filepath)) { char c; while ((c = (char) br.read()) != (char) -1) { if (c == '\n') diff --git a/src/main/java/org/aksw/iguana/cc/utils/IndexedQueryReader.java b/src/main/java/org/aksw/iguana/cc/utils/IndexedQueryReader.java index 38691061e..b89ee7ae6 100644 --- a/src/main/java/org/aksw/iguana/cc/utils/IndexedQueryReader.java +++ b/src/main/java/org/aksw/iguana/cc/utils/IndexedQueryReader.java @@ -1,7 +1,16 @@ package org.aksw.iguana.cc.utils; +import org.apache.commons.io.input.AutoCloseInputStream; +import org.apache.commons.io.input.BoundedInputStream; + import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.*; import java.util.stream.Collectors; @@ -18,22 +27,24 @@ public class IndexedQueryReader { /** * This list stores the start position and the length of each indexed content. */ - private List indices; + private final List indices; - /** The file whose content should be indexed. */ - private final File file; + /** + * The file whose content should be indexed. + */ + private final Path path; /** * Indexes each content in between two of the given separators (including the beginning and end of the file). The * given separator isn't allowed to be empty. * - * @param filepath path to the file + * @param filepath path to the file * @param separator the separator line that is used in the file (isn't allowed to be empty) * @return reader to access the indexed content * @throws IllegalArgumentException the given separator was empty * @throws IOException */ - public static IndexedQueryReader makeWithStringSeparator(String filepath, String separator) throws IOException { + public static IndexedQueryReader makeWithStringSeparator(Path filepath, String separator) throws IOException { if (separator.isEmpty()) throw new IllegalArgumentException("Separator for makeWithStringSeparator can not be empty."); return new IndexedQueryReader(filepath, separator); @@ -48,7 +59,7 @@ public static IndexedQueryReader makeWithStringSeparator(String filepath, String * @return reader to access the indexed content * @throws IOException */ - public static IndexedQueryReader makeWithEmptyLines(String filepath) throws IOException { + public static IndexedQueryReader makeWithEmptyLines(Path filepath) throws IOException { String lineEnding = FileUtils.getLineEnding(filepath); return new IndexedQueryReader(filepath, lineEnding + lineEnding); } @@ -60,7 +71,7 @@ public static IndexedQueryReader makeWithEmptyLines(String filepath) throws IOEx * @return reader to access the indexed lines * @throws IOException */ - public static IndexedQueryReader make(String filepath) throws IOException { + public static IndexedQueryReader make(Path filepath) throws IOException { return new IndexedQueryReader(filepath, FileUtils.getLineEnding(filepath)); } @@ -68,13 +79,13 @@ public static IndexedQueryReader make(String filepath) throws IOException { * Creates an object that indexes each content in between two of the given separators (including the beginning and * end of the given file).
* - * @param filepath path to the file + * @param filepath path to the file * @param separator the separator for each query * @throws IOException */ - private IndexedQueryReader(String filepath, String separator) throws IOException { - this.file = new File(filepath); - this.indexFile(separator); + private IndexedQueryReader(Path filepath, String separator) throws IOException { + path = filepath; + indices = indexFile(path, separator); } /** @@ -86,14 +97,22 @@ private IndexedQueryReader(String filepath, String separator) throws IOException */ public String readQuery(int index) throws IOException { // Indexed queries can't be larger than ~2GB - byte[] data = new byte[Math.toIntExact(this.indices.get(index)[1])]; - String output; - try (RandomAccessFile raf = new RandomAccessFile(this.file, "r")) { - raf.seek(this.indices.get(index)[0]); - raf.read(data); - output = new String(data, StandardCharsets.UTF_8); + try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) { + final ByteBuffer buffer = ByteBuffer.allocate((int) indices.get(index)[1]); + final var read = channel.read(buffer, indices.get(index)[0]); + assert read == indices.get(index)[1]; + return new String(buffer.array(), StandardCharsets.UTF_8); } - return output; + } + + public InputStream streamQuery(int index) throws IOException { + return new AutoCloseInputStream( + new BufferedInputStream( + new BoundedInputStream( + Channels.newInputStream( + FileChannel.open(path, StandardOpenOption.READ) + .position(this.indices.get(index)[0] /* offset */)), + this.indices.get(index)[1] /* length */))); } /** @@ -124,12 +143,13 @@ public int size() { * separators too. * * @param separator the custom separator + * @return the Indexes * @throws IOException */ - private void indexFile(String separator) throws IOException { - try (FileInputStream fi = new FileInputStream(file); + private static List indexFile(Path filepath, String separator) throws IOException { + try (InputStream fi = Files.newInputStream(filepath, StandardOpenOption.READ); BufferedInputStream bis = new BufferedInputStream(fi)) { - this.indices = FileUtils.indexStream(separator,bis) + return FileUtils.indexStream(separator, bis) .stream().filter((long[] e) -> e[1] > 0 /* Only elements with length > 0 */).collect(Collectors.toList()); } } diff --git a/src/main/java/org/aksw/iguana/cc/utils/ResultSizeRetriever.java b/src/main/java/org/aksw/iguana/cc/utils/ResultSizeRetriever.java deleted file mode 100644 index 097843606..000000000 --- a/src/main/java/org/aksw/iguana/cc/utils/ResultSizeRetriever.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryExecutionFactory; -import org.apache.jena.query.ResultSetFormatter; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.PrintWriter; - -/** - * Util class to retrieve the resultsize of a queryfile and an sparql endpoint. - */ -public class ResultSizeRetriever { - - public static void main(String[] args) { - if(args.length!=3) { - System.out.println("resretriever.sh http://endpoint queryfile.sparql outputfile.tsv"); - return; - } - int i=0; - try(BufferedReader reader = new BufferedReader(new FileReader(args[1]));PrintWriter pw = new PrintWriter(args[2])){ - String line; - while((line=reader.readLine())!=null) { - if(line.isEmpty()) { - continue; - } - try { - pw.println(i+"\t"+retrieveSize(args[0], line)); - }catch(Exception e) { - pw.println(i+"\t?"); - e.printStackTrace(); - } - System.out.println(i+" done"); - i++; - } - - }catch(Exception e) { - e.printStackTrace(); - } - } - - - public static int retrieveSize(String endpoint, String query) { - QueryExecution exec = QueryExecutionFactory.sparqlService(endpoint, query); - return ResultSetFormatter.consume(exec.execSelect()); - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/utils/SPARQLQueryStatistics.java b/src/main/java/org/aksw/iguana/cc/utils/SPARQLQueryStatistics.java deleted file mode 100644 index 14b58f8af..000000000 --- a/src/main/java/org/aksw/iguana/cc/utils/SPARQLQueryStatistics.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.apache.jena.query.Query; -import org.apache.jena.sparql.syntax.ElementWalker; - -/** - * Simple SPARQL Query statistics - */ -public class SPARQLQueryStatistics { - - public int aggr=0; - public int filter=0; - public int optional=0; - public int union=0; - public int having=0; - public int groupBy=0; - public int offset=0; - public double size=0.0; - public int orderBy=0; - public int triples=0; - - - /** - * Will add the stats of the provided query to this statistics count. - * @param q - */ - public void getStatistics(Query q) { - if(q.isSelectType()) { - - size++; - offset+=q.hasOffset()?1:0; - aggr+=q.hasAggregators()?1:0; - groupBy+=q.hasGroupBy()?1:0; - having+=q.hasHaving()?1:0; - orderBy+=q.hasOrderBy()?1:0; - - StatisticsVisitor visitor = new StatisticsVisitor(); - visitor.setElementWhere(q.getQueryPattern()); - ElementWalker.walk(q.getQueryPattern(), visitor); - - union+=visitor.union?1:0; - optional+=visitor.optional?1:0; - filter+=visitor.filter?1:0; - triples += visitor.bgps; - - } - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/utils/StatisticsVisitor.java b/src/main/java/org/aksw/iguana/cc/utils/StatisticsVisitor.java deleted file mode 100644 index c1d033b0b..000000000 --- a/src/main/java/org/aksw/iguana/cc/utils/StatisticsVisitor.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.apache.jena.sparql.syntax.*; - - -/** - * Simple visitor to check if simple statistics of a SPARQL Query appeared. - */ -public class StatisticsVisitor extends RecursiveElementVisitor{ - - public boolean filter; - public boolean regexFilter=false; - public boolean cmpFilter=false; - public boolean union; - public boolean optional; - private boolean started; - private Element where; - public int bgps; - - public StatisticsVisitor() { - super(new ElementVisitorBase()); - } - - public void startElement(ElementGroup el) { - if (!started && el.equals(where)) { - // root element found - started = true; - - } - } - - public void setElementWhere(Element el) { - this.where = el; - } - - public void endElement(ElementPathBlock el) { - - if (started) { - bgps+=el.getPattern().getList().size(); - } - - } - - public void startElement(ElementFilter el) {this.filter=true;el.getExpr();} - public void startElement(ElementUnion el) {this.union=true;} - public void startElement(ElementOptional el) {this.optional=true;} - -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/AbstractWorker.java b/src/main/java/org/aksw/iguana/cc/worker/AbstractWorker.java deleted file mode 100644 index 9ffed99c7..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/AbstractWorker.java +++ /dev/null @@ -1,280 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.query.handler.QueryHandler; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.protocol.HttpContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Instant; -import java.util.*; - - -/** - * The Abstract Worker which will implement the runnable, the main loop, the - * time to wait before a query and will send the results to the ResultProcessor - * module
- * so the Implemented Workers only need to implement which query to test next - * and how to test this query. - * - * @author f.conrads - */ -public abstract class AbstractWorker implements Worker { - protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractWorker.class); - - protected String taskID; - - /** - * The unique ID of the worker, should be from 0 to n - */ - protected Integer workerID; - - /** - * The worker Type. f.e. SPARQL or UPDATE or SQL or whatever - * Determined by the Shorthand of the class, if no Shorthand is provided the class name is used. - * The workerType is only used in logging messages. - */ - protected String workerType; - protected ConnectionConfig connection; - protected Map queries; - - protected Double timeLimit; - protected Double timeOut = 180000D; - protected Integer fixedLatency = 0; - protected Integer gaussianLatency = 0; - - protected boolean endSignal = false; - protected long executedQueries; - protected Instant startTime; - protected ConnectionConfig con; - protected int queryHash; - protected QueryHandler queryHandler; - protected Collection results = new LinkedList<>(); - private Random latencyRandomizer; - private Long endAtNOQM = null; - - public AbstractWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency) { - this.taskID = taskID; - this.workerID = workerID; - this.con = connection; - - handleTimeParams(timeLimit, timeOut, fixedLatency, gaussianLatency); - setWorkerType(); - - this.queryHandler = new QueryHandler(queries, this.workerID); - - LOGGER.debug("Initialized new Worker[{{}} : {{}}] for taskID {{}}", this.workerType, workerID, taskID); - } - - - @Override - public void waitTimeMs() { - double wait = this.fixedLatency.doubleValue(); - double gaussian = this.latencyRandomizer.nextDouble(); - wait += (gaussian * 2) * this.gaussianLatency; - LOGGER.debug("Worker[{} : {}]: Time to wait for next Query {}", this.workerType, this.workerID, wait); - try { - if (wait > 0) - Thread.sleep((int) wait); - } catch (InterruptedException e) { - LOGGER.error("Worker[{{}} : {}]: Could not wait time before next query due to: {}", this.workerType, this.workerID, e); - } - } - - /** - * This will start the worker. It will get the next query, wait as long as it - * should wait before executing the next query, then it will test the query and - * send it if not aborted yet to the ResultProcessor Module - */ - public void startWorker() { - // For Update and Logging purpose get startTime of Worker - this.startTime = Instant.now(); - - this.queryHash = this.queryHandler.hashCode(); - - LOGGER.info("Starting Worker[{{}} : {{}}].", this.workerType, this.workerID); - // Execute Queries as long as the Stresstest will need. - while (!this.endSignal && !hasExecutedNoOfQueryMixes(this.endAtNOQM)) { - // Get next query - StringBuilder query = new StringBuilder(); - StringBuilder queryID = new StringBuilder(); - try { - getNextQuery(query, queryID); - // check if endsignal was triggered - if (this.endSignal) { - break; - } - } catch (IOException e) { - LOGGER.error( - "Worker[{{}} : {{}}] : Something went terrible wrong in getting the next query. Worker will be shut down.", - this.workerType, this.workerID); - LOGGER.error("Error which occured:_", e); - break; - } - // Simulate Network Delay (or whatever should be simulated) - waitTimeMs(); - - // benchmark query - try { - executeQuery(query.toString(), queryID.toString()); - } catch (Exception e) { - LOGGER.error("Worker[{{}} : {{}}] : ERROR with query: {{}}", this.workerType, this.workerID, query); - } - //this.executedQueries++; - } - LOGGER.info("Stopping Worker[{{}} : {{}}].", this.workerType, this.workerID); - } - - @Override - public void getNextQuery(StringBuilder query, StringBuilder queryID) throws IOException { - this.queryHandler.getNextQuery(query, queryID); - } - - protected HttpContext getAuthContext(String endpoint) { - HttpClientContext context = HttpClientContext.create(); - - if (this.con.getPassword() != null && this.con.getUser() != null && !this.con.getPassword().isEmpty() && !this.con.getUser().isEmpty()) { - CredentialsProvider provider = new BasicCredentialsProvider(); - - provider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), - new UsernamePasswordCredentials(this.con.getUser(), this.con.getPassword())); - - //create target host - String targetHost = endpoint; - try { - URI uri = new URI(endpoint); - targetHost = uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort(); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - //set Auth cache - AuthCache authCache = new BasicAuthCache(); - BasicScheme basicAuth = new BasicScheme(); - authCache.put(HttpHost.create(targetHost), basicAuth); - - context.setCredentialsProvider(provider); - context.setAuthCache(authCache); - - } - return context; - } - - public synchronized void addResults(QueryExecutionStats results) { - // TODO: check if statement for bugs, if the if line exists in the UpdateWorker, the UpdateWorker fails its tests - if (!this.endSignal && !hasExecutedNoOfQueryMixes(this.endAtNOQM)) { - this.results.add(results); - this.executedQueries++; - - // - if (getNoOfQueries() > 0 && getExecutedQueries() % getNoOfQueries() == 0) { - LOGGER.info("Worker executed {} queryMixes", getExecutedQueries() * 1.0 / getNoOfQueries()); - } - } - } - - @Override - public synchronized Collection popQueryResults() { - if (this.results.isEmpty()) { - return null; - } - Collection ret = this.results; - this.results = new LinkedList<>(); - return ret; - } - - @Override - public long getExecutedQueries() { - return this.executedQueries; - } - - @Override - public void stopSending() { - this.endSignal = true; - LOGGER.debug("Worker[{{}} : {{}}] got stop signal.", this.workerType, this.workerID); - } - - @Override - public boolean isTerminated() { - return this.endSignal; - } - - - @Override - public void run() { - startWorker(); - } - - @Override - public long getNoOfQueries() { - return this.queryHandler.getQueryCount(); - } - - @Override - public boolean hasExecutedNoOfQueryMixes(Long noOfQueryMixes) { - if (noOfQueryMixes == null) { - return false; - } - return getExecutedQueries() / (getNoOfQueries() * 1.0) >= noOfQueryMixes; - } - - @Override - public void endAtNoOfQueryMixes(Long noOfQueryMixes) { - this.endAtNOQM = noOfQueryMixes; - } - - @Override - public QueryHandler getQueryHandler() { - return this.queryHandler; - } - - private void handleTimeParams(Integer timeLimit, Integer timeOut, Integer fixedLatency, Integer gaussianLatency) { - if (timeLimit != null) { - this.timeLimit = timeLimit.doubleValue(); - } - if (timeOut != null) { - this.timeOut = timeOut.doubleValue(); - } - if (fixedLatency != null) { - this.fixedLatency = fixedLatency; - } - if (gaussianLatency != null) { - this.gaussianLatency = gaussianLatency; - } - this.latencyRandomizer = new Random(this.workerID); - } - - private void setWorkerType() { - if (this.getClass().getAnnotation(Shorthand.class) != null) { - this.workerType = this.getClass().getAnnotation(Shorthand.class).value(); - } else { - this.workerType = this.getClass().getName(); - } - } - - @Override - public WorkerMetadata getMetadata() { - return new WorkerMetadata( - this.workerID, - this.workerType, - this.timeOut, - this.queryHandler.getQueryCount(), - this.queryHandler.hashCode(), - this.queryHandler.getAllQueryIds() - ); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/HttpWorker.java b/src/main/java/org/aksw/iguana/cc/worker/HttpWorker.java new file mode 100644 index 000000000..6e0242d9a --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/worker/HttpWorker.java @@ -0,0 +1,161 @@ +package org.aksw.iguana.cc.worker; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import org.aksw.iguana.cc.query.handler.QueryHandler; +import org.aksw.iguana.cc.query.selector.QuerySelector; +import org.aksw.iguana.cc.tasks.impl.Stresstest; +import org.aksw.iguana.cc.worker.impl.SPARQLProtocolWorker; + +import java.net.http.HttpTimeoutException; +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Base64; +import java.util.List; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * Interface for the Worker Thread used in the {@link Stresstest} + */ +public abstract class HttpWorker { + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = SPARQLProtocolWorker.Config.class, name = "SPARQLProtocolWorker"), + }) + public interface Config { + CompletionTarget completionTarget(); + + String acceptHeader(); + + /** + * Returns the number of workers with this configuration that will be started. + * + * @return the number of workers + */ + Integer number(); + + /** + * Determines whether the results should be parsed based on the acceptHeader. + * + * @return true if the results should be parsed, false otherwise + */ + Boolean parseResults(); + + QueryHandler queries(); + + ConnectionConfig connection(); + + Duration timeout(); + } + + public record ExecutionStats( + int queryID, + Instant startTime, + Duration duration, // should always exist + Optional httpStatusCode, + OptionalLong contentLength, + OptionalLong responseBodyHash, + Optional error + ) { + public enum END_STATE { + SUCCESS(0), + TIMEOUT(110), // ETIMEDOUT - Connection timed out + HTTP_ERROR(111), // ECONNREFUSED - Connection refused + MISCELLANEOUS_EXCEPTION(1); + + public final int value; + END_STATE(int value) { + this.value = value; + } + } + + public END_STATE endState() { + if (successful()) { + return END_STATE.SUCCESS; + } else if (timeout()) { + return END_STATE.TIMEOUT; + } else if (httpError()) { + return END_STATE.HTTP_ERROR; + } else { + return END_STATE.MISCELLANEOUS_EXCEPTION; + } + } + + public boolean completed() { + return httpStatusCode().isPresent(); + } + + public boolean successful() { + return error.isEmpty() && httpStatusCode.orElse(0) / 100 == 2; + } + + public boolean timeout() { + boolean timeout = false; + if (!successful() && error().isPresent()) { + timeout |= error().get() instanceof java.util.concurrent.TimeoutException; + if (error().get() instanceof ExecutionException exec) { + timeout = exec.getCause() instanceof HttpTimeoutException; + } + } + return timeout; + } + + public boolean httpError() { + if (httpStatusCode.isEmpty()) + return false; + return httpStatusCode().orElse(0) / 100 != 2; + } + + public boolean miscellaneousException() { + return error().isPresent() && !timeout() && !httpError(); + } + } + + public record Result(long workerID, List executionStats, ZonedDateTime startTime, ZonedDateTime endTime) {} + + @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) + @JsonSubTypes({ + @JsonSubTypes.Type(value = TimeLimit.class), + @JsonSubTypes.Type(value = QueryMixes.class) + }) + sealed public interface CompletionTarget permits TimeLimit, QueryMixes {} + + public record TimeLimit(@JsonProperty(required = true) Duration duration) implements CompletionTarget {} + + public record QueryMixes(@JsonProperty(required = true) int number) implements CompletionTarget {} + + final protected long workerID; + final protected Config config; + final protected ResponseBodyProcessor responseBodyProcessor; + final protected QuerySelector querySelector; + + public HttpWorker(long workerID, ResponseBodyProcessor responseBodyProcessor, Config config) { + this.workerID = workerID; + this.responseBodyProcessor = responseBodyProcessor; + this.config = config; + this.querySelector = this.config.queries().getQuerySelectorInstance(); + } + + public static String basicAuth(String username, String password) { + return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + } + + public abstract CompletableFuture start(); + + public Config config() { + return this.config; + } + + public long getWorkerID() { + return this.workerID; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/worker/LatencyStrategy.java b/src/main/java/org/aksw/iguana/cc/worker/LatencyStrategy.java deleted file mode 100644 index dfcca2a14..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/LatencyStrategy.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.aksw.iguana.cc.worker; - -/** - * The Strategy Names to simulate different network latency behaviors - * - * @author f.conrads - * - */ -public enum LatencyStrategy { - /** - * No Latency should be simulated - */ - NONE, - - /** - * A fixed time/ms should be waited between queries (time is the latency base value) - */ - FIXED, - - /** - * The time/ms should be calculated randomly each time - * out of a gaussian intervall based on the latency base value as follows - * - * [0;2*latencyBaseValue] - */ - VARIABLE -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/ResponseBodyProcessor.java b/src/main/java/org/aksw/iguana/cc/worker/ResponseBodyProcessor.java new file mode 100644 index 000000000..3cd6e56a2 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/worker/ResponseBodyProcessor.java @@ -0,0 +1,82 @@ +package org.aksw.iguana.cc.worker; + +import org.aksw.iguana.cc.lang.LanguageProcessor; +import org.aksw.iguana.commons.io.BigByteArrayInputStream; +import org.aksw.iguana.commons.io.BigByteArrayOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.MessageFormat; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.*; + +public class ResponseBodyProcessor { + public record Config(String contentType, Integer threads) { + public Config(String contentType, Integer threads) { + this.contentType = contentType; + this.threads = threads == null ? 1 : threads; + } + } + + public record Key(long contentLength, long xxh64) {} + + public ResponseBodyProcessor(Config config) { + this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(config.threads == null ? 1 : config.threads); + this.languageProcessor = LanguageProcessor.getInstance(config.contentType); + } + + public ResponseBodyProcessor(String contentType) { + this(new Config(contentType, null)); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(ResponseBodyProcessor.class); + + private final ConcurrentHashMap.KeySetView seenResponseBodies = ConcurrentHashMap.newKeySet(); + + private final List responseDataMetrics = Collections.synchronizedList(new ArrayList<>()); + private final LanguageProcessor languageProcessor; + + private final ThreadPoolExecutor executor; + + public boolean add(long contentLength, long xxh64, BigByteArrayOutputStream bbaos) { + final var key = new Key(contentLength, xxh64); + if (seenResponseBodies.add(key)) { + submit(key, bbaos); + return true; + } + return false; + } + + private void submit(Key key, BigByteArrayOutputStream bigByteArrayOutputStream) { + executor.execute(() -> { + var processingResult = languageProcessor.process(new BigByteArrayInputStream(bigByteArrayOutputStream), key.xxh64); + responseDataMetrics.add(processingResult); + }); + } + + public List getResponseDataMetrics() { + if (executor.isTerminated()) { + return responseDataMetrics; + } + + final var timeout = Duration.ofMinutes(10); + LOGGER.info(MessageFormat.format("Shutting down ResponseBodyProcessor with {0}min timeout to finish processing. {1} tasks remaining.", timeout.toMinutes(), executor.getQueue().size())); + boolean noTimeout; + try { + executor.shutdown(); + noTimeout = executor.awaitTermination(10, TimeUnit.MINUTES); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (noTimeout) LOGGER.info("ResponseBodyProcessor completed."); + else LOGGER.warn("ResponseBodyProcessor timed out."); + return responseDataMetrics; + } + + public LanguageProcessor getLanguageProcessor() { + return this.languageProcessor; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/worker/ResponseBodyProcessorInstances.java b/src/main/java/org/aksw/iguana/cc/worker/ResponseBodyProcessorInstances.java new file mode 100644 index 000000000..95be1f0e9 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/worker/ResponseBodyProcessorInstances.java @@ -0,0 +1,44 @@ +package org.aksw.iguana.cc.worker; + +import org.aksw.iguana.cc.lang.LanguageProcessor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class ResponseBodyProcessorInstances { + final private Map processors = new HashMap<>(); + + public ResponseBodyProcessorInstances() {} + + public ResponseBodyProcessorInstances(List configs) { + if (configs == null) return; + for (var config : configs) { + processors.put(config.contentType(), new ResponseBodyProcessor(config)); + } + } + + public ResponseBodyProcessor getProcessor(String contentType) { + if (!processors.containsKey(contentType)) { + processors.put(contentType, new ResponseBodyProcessor(contentType)); + } + return processors.get(contentType); + } + + /** + * Returns a Supplier that returns the results of all ResponseBodyProcessors. A supplier is used for data + * abstraction. + * + * @return supplier for all results + */ + public Supplier>> getResults() { + return () -> { // TODO: consider removing the languageProcessor as the key, it's only used right now for creating strings for naming + Map> out = new HashMap<>(); + for (var processor : processors.values()) { + out.put(processor.getLanguageProcessor(), processor.getResponseDataMetrics()); + } + return out; + }; + } +} diff --git a/src/main/java/org/aksw/iguana/cc/worker/Worker.java b/src/main/java/org/aksw/iguana/cc/worker/Worker.java deleted file mode 100644 index fb7076895..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/Worker.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.query.handler.QueryHandler; -import org.aksw.iguana.cc.tasks.stresstest.Stresstest; - -import java.io.IOException; -import java.util.Collection; - -/** - * Interface for the Worker Thread used in the {@link Stresstest} - * - * @author f.conrads - * - */ -public interface Worker extends Runnable{ - - - /** - * This method executes a query and adds the results to the Result Processor for proper result and metric calculations. - * Note: Some of the Worker implementations employ background threads to process the result of the query. - * Due to this, this method does not return anything and each implementation of this method must also add the - * results to Result Processor within this method. This can be done by calling AbstractWorker.addResults(QueryExecutionStats) - * - * @param query The query which should be executed - * @param queryID the ID of the query which should be executed - */ - void executeQuery(String query, String queryID); - - /** - * This method saves the next query in the queryStr StringBuilder and - * the query id in the queryID. - * - * @param queryStr The query should be stored in here! - * @param queryID The queryID should be stored in here! - * @throws IOException - */ - void getNextQuery(StringBuilder queryStr, StringBuilder queryID) throws IOException; - - /** - * This method will return the query handler which is used by this worker - * - * @return QueryHandler which is used by this worker - */ - QueryHandler getQueryHandler(); - - - /** - * This should stop the next sending process. - * If an execution started before this method was called, but answered after, it should not be counted! - */ - void stopSending(); - - /** - * This will simulate the Time in ms to wait before testing the next query. - * It can be used to simulate network delay. - */ - void waitTimeMs(); - - - /** - * This will return the amount of executed queries so far - * - * @return no. of executed queries - */ - long getExecutedQueries(); - - /** - * Get and remove all internal stored results of finished queries - * - * @return list of Properties to send to RabbitMQ - */ - Collection popQueryResults(); - - boolean isTerminated(); - - /** - * Returns the no of queries in the queryset of the worker - * @return no of queries in the queryset - */ - long getNoOfQueries(); - - /** - * Returns if the no of query mixes were already executed - * @param noOfQueryMixes no of query mixes - * @return true if the no of query mixes were already executed - */ - boolean hasExecutedNoOfQueryMixes(Long noOfQueryMixes); - - - /** - * Sets the end restriction - * - * @param noOfQueryMixes after which the worker should stop - */ - void endAtNoOfQueryMixes(Long noOfQueryMixes); - - WorkerMetadata getMetadata(); -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/WorkerFactory.java b/src/main/java/org/aksw/iguana/cc/worker/WorkerFactory.java deleted file mode 100644 index 401cf5fdb..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/WorkerFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.commons.factory.TypedFactory; - -/** - * Factory to create a {@link Worker} - * - * @author f.conrads - * - */ -public class WorkerFactory extends TypedFactory{ - - -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/WorkerMetadata.java b/src/main/java/org/aksw/iguana/cc/worker/WorkerMetadata.java deleted file mode 100644 index 678306aba..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/WorkerMetadata.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.aksw.iguana.cc.worker; - -public record WorkerMetadata( - int workerID, - String workerType, - double timeout, - int numberOfQueries, - int queryHash, - String[] queryIDs -) {} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputFileWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputFileWorker.java deleted file mode 100644 index 5660a73d6..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputFileWorker.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Map; - -/** - * Worker to execute a query against a CLI process, the connection.service will be the command to execute the query against. - *

- * Assumes that the CLI process won't stop but will just accepts queries one after another and returns the results in the CLI output. - * also assumes that the query has to be read from a file instead of plain input - *

- * This worker can be set to be created multiple times in the background if one process will throw an error, a backup process was already created and can be used. - * This is handy if the process won't just prints an error message, but simply exits. - */ -@Shorthand("CLIInputFileWorker") -public class CLIInputFileWorker extends MultipleCLIInputWorker { - private final String dir; - - public CLIInputFileWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, String initFinished, String queryFinished, String queryError, @Nullable Integer numberOfProcesses, String directory) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency, initFinished, queryFinished, queryError, numberOfProcesses); - this.dir = directory; - } - - @Override - protected String writableQuery(String query) { - File f; - - try { - new File(this.dir).mkdirs(); - f = new File(this.dir + File.separator + "tmpquery.sparql"); - f.createNewFile(); - f.deleteOnExit(); - try (PrintWriter pw = new PrintWriter(f)) { - pw.print(query); - } - return f.getName(); - } catch (IOException e) { - e.printStackTrace(); - } - - return query; - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputPrefixWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputPrefixWorker.java deleted file mode 100644 index e21120219..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputPrefixWorker.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; - -import java.util.Map; - -/** - * Worker to execute a query against a CLI process, the connection.service will be the command to execute the query against. - *

- * Assumes that the CLI process won't stop but will just accepts queries one after another and returns the results in the CLI output. - * also assumes that the query has to be prefixed and suffixed. - * For example: SPARQL SELECT * {?s ?p ?o} ; whereas 'SPARQL' is the prefix and ';' is the suffix. - *

- * This worker can be set to be created multiple times in the background if one process will throw an error, a backup process was already created and can be used. - * This is handy if the process won't just prints an error message, but simply exits. - */ -@Shorthand("CLIInputPrefixWorker") -public class CLIInputPrefixWorker extends MultipleCLIInputWorker { - - private final String prefix; - private final String suffix; - - public CLIInputPrefixWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, String initFinished, String queryFinished, String queryError, @Nullable Integer numberOfProcesses, String queryPrefix, String querySuffix) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency, initFinished, queryFinished, queryError, numberOfProcesses); - this.prefix = queryPrefix; - this.suffix = querySuffix; - } - - @Override - protected String writableQuery(String query) { - return this.prefix + " " + query + " " + this.suffix; - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputWorker.java deleted file mode 100644 index 70b30acbb..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIInputWorker.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.utils.CLIProcessManager; -import org.aksw.iguana.cc.worker.AbstractWorker; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -import static org.aksw.iguana.commons.time.TimeUtils.durationInMilliseconds; - -/** - * Worker to execute a query against a CLI process, the connection.service will be the command to execute the query against. - *

- * Assumes that the CLI process won't stop but will just accepts queries one after another and returns the results in the CLI output. - */ -@Shorthand("CLIInputWorker") -public class CLIInputWorker extends AbstractWorker { - - private final Logger LOGGER = LoggerFactory.getLogger(getClass()); - private final String initFinished; - private final String queryFinished; - private final String error; - private Process process; - - public CLIInputWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, String initFinished, String queryFinished, String queryError) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency); - this.initFinished = initFinished; - this.queryFinished = queryFinished; - this.error = queryError; - this.setWorkerProperties(); - } - - private void setWorkerProperties() { - // Create a CLI process, initialize it - this.process = CLIProcessManager.createProcess(this.con.getEndpoint()); - try { - CLIProcessManager.countLinesUntilStringOccurs(process, this.initFinished, this.error); //Init - } catch (IOException e) { - LOGGER.error("Exception while trying to wait for init of CLI Process", e); - } - } - - @Override - public void executeQuery(String query, String queryID) { - Instant start = Instant.now(); - - try { - // Create background thread that will watch the output of the process and prepare results - AtomicLong size = new AtomicLong(-1); - AtomicBoolean failed = new AtomicBoolean(false); - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.execute(() -> { - try { - LOGGER.debug("Process Alive: {}", this.process.isAlive()); - LOGGER.debug("Reader ready: {}", CLIProcessManager.isReaderReady(this.process)); - size.set(CLIProcessManager.countLinesUntilStringOccurs(this.process, this.queryFinished, this.error)); - } catch (IOException e) { - failed.set(true); - } - }); - - // Execute the query on the process - try { - if (this.process.isAlive()) { - CLIProcessManager.executeCommand(this.process, writableQuery(query)); - } else if (this.endSignal) { - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - return; - } else { - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - return; - } - } finally { - executor.shutdown(); - executor.awaitTermination((long) (double) this.timeOut, TimeUnit.MILLISECONDS); - } - - // At this point, query is executed and background thread has processed the results. - // Next, calculate time for benchmark. - double duration = durationInMilliseconds(start, Instant.now()); - - if (duration >= this.timeOut) { - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_SOCKET_TIMEOUT, duration)); - return; - } else if (failed.get()) { - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, duration)); - return; - } - - // SUCCESS - LOGGER.debug("Query successfully executed size: {}", size.get()); - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_SUCCESS, duration, size.get())); - } catch (IOException | InterruptedException e) { - LOGGER.warn("Exception while executing query ", e); - // ERROR - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - } - } - - - protected String writableQuery(String query) { - return query; - } - - - @Override - public void stopSending() { - super.stopSending(); - this.process.destroyForcibly(); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/CLIWorker.java deleted file mode 100644 index fb5b0fd1a..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/CLIWorker.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.worker.AbstractWorker; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Map; - -import static org.aksw.iguana.commons.time.TimeUtils.durationInMilliseconds; - -/** - * Worker to execute a query again a CLI process, the connection.service will be the command to execute the query against. - *

- * command may look like the following

- * cliprocess.sh $QUERY$ $USER$ $PASSWORD$ - *
- * whereas $QUERY$ will be exchanged with the actual query as well as user and password. - * Further on it is possible to encode the query using $ENCODEDQUERY$ instead of $QUERY$ - */ -@Shorthand("CLIWorker") -public class CLIWorker extends AbstractWorker { - - private final Logger LOGGER = LoggerFactory.getLogger(getClass()); - - public CLIWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency); - } - - @Override - public void executeQuery(String query, String queryID) { - Instant start = Instant.now(); - // use cli as service - String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); - String queryCLI = getReplacedQuery(query, encodedQuery); - // execute queryCLI and read response - ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true); - processBuilder.command("bash", "-c", queryCLI); - try { - - Process process = processBuilder.start(); - - long size = -1; - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - - // -1 as the first line should be the header - while (reader.readLine() != null) { - size++; - } - } catch (Exception e) { - e.printStackTrace(); - } - int exitVal = process.waitFor(); - if (exitVal == 0) { - LOGGER.debug("Query successfully executed size: {}", size); - } else { - LOGGER.warn("Exit Value of Process was not 0, was {} ", exitVal); - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - return; - } - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_SUCCESS, durationInMilliseconds(start, Instant.now()), size)); - return; - } catch (Exception e) { - LOGGER.warn("Unknown Exception while executing query", e); - } - // ERROR - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - } - - private String getReplacedQuery(String query, String encodedQuery) { - String queryCLI = this.con.getEndpoint().replace("$QUERY$", query); - queryCLI = queryCLI.replace("$ENCODEDQUERY$", encodedQuery); - - if (this.con.getUser() != null) { - queryCLI = queryCLI.replace("$USER$", this.con.getUser()); - } else { - queryCLI = queryCLI.replace("$USER$", ""); - - } - if (this.con.getPassword() != null) { - queryCLI = queryCLI.replace("$PASSWORD$", this.con.getPassword()); - } else { - queryCLI = queryCLI.replace("$PASSWORD$", ""); - - } - return queryCLI; - } -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/HttpGetWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/HttpGetWorker.java deleted file mode 100644 index 857d0395c..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/HttpGetWorker.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.apache.http.HttpHeaders; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpGet; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Map; - - -/** - * HTTP Get Worker. - * Uses HTTP Get to execute a Query.

- * if the parameter type was not set it will use 'query' as the parameter as default, otherwise it will use the provided parameter - */ -@Shorthand("HttpGetWorker") -public class HttpGetWorker extends HttpWorker { - - protected String parameter = "query"; - - protected String responseType = null; - - public HttpGetWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, @Nullable String parameterName, @Nullable String responseType) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency); - - if (parameterName != null) { - this.parameter = parameterName; - } - if (responseType != null) { - this.responseType = responseType; - } - } - - void buildRequest(String query, String queryID) { - String qEncoded = URLEncoder.encode(query, StandardCharsets.UTF_8); - String addChar = "?"; - if (this.con.getEndpoint().contains("?")) { - addChar = "&"; - } - String url = this.con.getEndpoint() + addChar + this.parameter + "=" + qEncoded; - this.request = new HttpGet(url); - RequestConfig requestConfig = - RequestConfig.custom() - .setSocketTimeout(this.timeOut.intValue()) - .setConnectTimeout(this.timeOut.intValue()) - .build(); - - if (this.responseType != null) - this.request.setHeader(HttpHeaders.ACCEPT, this.responseType); - - this.request.setConfig(requestConfig); - } -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/HttpPostWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/HttpPostWorker.java deleted file mode 100644 index c6f884dac..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/HttpPostWorker.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.apache.http.HttpHeaders; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -/** - * HTTP Post worker. - * Uses HTTP posts to execute a query. - *

- * Sends the query in plain as POST data if parameter type was not set, otherwise uses json as follows:
- * {PARAMETER: QUERY} - */ -@Shorthand("HttpPostWorker") -public class HttpPostWorker extends HttpGetWorker { - - private String contentType = "text/plain"; - - public HttpPostWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, @Nullable String parameterName, @Nullable String responseType, @Nullable String contentType) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency, parameterName, responseType); - - if (parameterName == null) { - this.parameter = null; - } - if (contentType != null) { - this.contentType = contentType; - } - } - - void buildRequest(String query, String queryID) { - StringBuilder data = new StringBuilder(); - if (parameter != null) { - String qEncoded = URLEncoder.encode(query, StandardCharsets.UTF_8); - data.append("{ \"").append(parameter).append("\": \"").append(qEncoded).append("\"}"); - } else { - data.append(query); - } - StringEntity entity = new StringEntity(data.toString(), StandardCharsets.UTF_8); - request = new HttpPost(con.getUpdateEndpoint()); - ((HttpPost) request).setEntity(entity); - request.setHeader("Content-Type", contentType); - RequestConfig requestConfig = RequestConfig.custom() - .setSocketTimeout(timeOut.intValue()) - .setConnectTimeout(timeOut.intValue()) - .build(); - - if (this.responseType != null) - request.setHeader(HttpHeaders.ACCEPT, this.responseType); - - request.setConfig(requestConfig); - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/HttpWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/HttpWorker.java deleted file mode 100644 index d9151232b..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/HttpWorker.java +++ /dev/null @@ -1,296 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.model.QueryResultHashKey; -import org.aksw.iguana.cc.worker.AbstractWorker; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.constants.COMMON; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.BasicHttpClientConnectionManager; -import org.apache.http.message.BasicHeader; -import org.json.simple.parser.ParseException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.*; - -import static org.aksw.iguana.commons.time.TimeUtils.durationInMilliseconds; - -/** - * Abstract HTTP worker - */ -public abstract class HttpWorker extends AbstractWorker { - - - protected final ExecutorService resultProcessorService = Executors.newFixedThreadPool(5); - protected ScheduledThreadPoolExecutor timeoutExecutorPool = new ScheduledThreadPoolExecutor(1); - protected ConcurrentMap processedResults = new ConcurrentHashMap<>(); - protected CloseableHttpClient client; - protected HttpRequestBase request; - protected ScheduledFuture abortCurrentRequestFuture; - protected CloseableHttpResponse response; - protected boolean resultsSaved = false; - protected boolean requestTimedOut = false; - protected String queryId; - protected Instant requestStartTime; - protected long tmpExecutedQueries = 0; - - public HttpWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency); - this.timeoutExecutorPool.setRemoveOnCancelPolicy(true); - } - - public ConcurrentMap getProcessedResults() { - return this.processedResults; - } - - protected void setTimeout(int timeOut) { - assert (this.request != null); - this.abortCurrentRequestFuture = this.timeoutExecutorPool.schedule( - () -> { - synchronized (this) { - this.request.abort(); - this.requestTimedOut = true; - } - }, - timeOut, TimeUnit.MILLISECONDS); - } - - protected void abortTimeout() { - if (!this.abortCurrentRequestFuture.isDone()) { - this.abortCurrentRequestFuture.cancel(false); - } - } - - - @Override - public void stopSending() { - super.stopSending(); - abortTimeout(); - try { - if (this.request != null && !this.request.isAborted()) { - this.request.abort(); - } - } catch (Exception ignored) { - } - closeClient(); - shutdownResultProcessor(); - } - - - public void shutdownResultProcessor() { - this.resultProcessorService.shutdown(); - try { - boolean finished = this.resultProcessorService.awaitTermination(3000, TimeUnit.MILLISECONDS); - if (!finished) { - LOGGER.error("Result Processor could be shutdown orderly. Terminating."); - this.resultProcessorService.shutdownNow(); - } - } catch (InterruptedException e) { - LOGGER.error("Could not shut down http result processor: " + e.getLocalizedMessage()); - } - - try { - boolean finished = this.timeoutExecutorPool.awaitTermination(3000, TimeUnit.MILLISECONDS); - if (!finished) { - LOGGER.error("Timeout Executor could be shutdown orderly. Terminating."); - this.timeoutExecutorPool.shutdownNow(); - } - } catch (InterruptedException e) { - LOGGER.error("Could not shut down http timout executor: " + e.getLocalizedMessage()); - } - } - - synchronized protected void addResultsOnce(QueryExecutionStats queryExecutionStats) { - if (!this.resultsSaved) { - addResults(queryExecutionStats); - this.resultsSaved = true; - } - } - - @Override - public void executeQuery(String query, String queryID) { - this.queryId = queryID; - this.resultsSaved = false; - this.requestTimedOut = false; - - if (this.client == null) - initClient(); - - try { - buildRequest(query, this.queryId); - - setTimeout(this.timeOut.intValue()); - - this.requestStartTime = Instant.now(); - this.response = this.client.execute(this.request, getAuthContext(this.con.getEndpoint())); - // method to process the result in background - processHttpResponse(); - - abortTimeout(); - - } catch (ClientProtocolException e) { - handleException(query, COMMON.QUERY_HTTP_FAILURE, e); - } catch (IOException e) { - if (this.requestTimedOut) { - LOGGER.warn("Worker[{} : {}]: Reached timeout on query (ID {})\n{}", - this.workerType, this.workerID, this.queryId, query); - addResultsOnce(new QueryExecutionStats(this.queryId, COMMON.QUERY_SOCKET_TIMEOUT, this.timeOut)); - } else { - handleException(query, COMMON.QUERY_UNKNOWN_EXCEPTION, e); - } - } catch (Exception e) { - handleException(query, COMMON.QUERY_UNKNOWN_EXCEPTION, e); - } finally { - abortTimeout(); - closeResponse(); - } - } - - private void handleException(String query, Long cause, Exception e) { - double duration = durationInMilliseconds(this.requestStartTime, Instant.now()); - addResultsOnce(new QueryExecutionStats(this.queryId, cause, duration)); - LOGGER.warn("Worker[{} : {}]: {} on query (ID {})\n{}", - this.workerType, this.workerID, e.getMessage(), this.queryId, query); - closeClient(); - initClient(); - } - - protected void processHttpResponse() { - int responseCode = this.response.getStatusLine().getStatusCode(); - boolean responseCodeSuccess = responseCode >= 200 && responseCode < 300; - boolean responseCodeOK = responseCode == 200; - - if (responseCodeOK) { // response status is OK (200) - // get content type header - HttpEntity httpResponse = this.response.getEntity(); - Header contentTypeHeader = new BasicHeader(httpResponse.getContentType().getName(), httpResponse.getContentType().getValue()); - // get content stream - try (InputStream contentStream = httpResponse.getContent()) { - // read content stream with resultProcessor, return length, set string in StringBuilder. - ByteArrayOutputStream responseBody = new ByteArrayOutputStream(); - long length = this.queryHandler.getLanguageProcessor().readResponse(contentStream, responseBody); - this.tmpExecutedQueries++; - // check if such a result was already parsed and is cached - double duration = durationInMilliseconds(this.requestStartTime, Instant.now()); - synchronized (this) { - QueryResultHashKey resultCacheKey = new QueryResultHashKey(queryId, length); - if (this.processedResults.containsKey(resultCacheKey)) { - LOGGER.debug("found result cache key {} ", resultCacheKey); - Long preCalculatedResultSize = this.processedResults.get(resultCacheKey); - addResultsOnce(new QueryExecutionStats(this.queryId, COMMON.QUERY_SUCCESS, duration, preCalculatedResultSize)); - } else { - // otherwise: parse it. The parsing result is cached for the next time. - if (!this.endSignal) { - this.resultProcessorService.submit(new HttpResultProcessor(this, this.queryId, duration, contentTypeHeader, responseBody, length)); - this.resultsSaved = true; - } - } - } - - } catch (IOException | TimeoutException e) { - double duration = durationInMilliseconds(this.requestStartTime, Instant.now()); - addResultsOnce(new QueryExecutionStats(this.queryId, COMMON.QUERY_HTTP_FAILURE, duration)); - } - } else if (responseCodeSuccess) { // response status is succeeded (2xx) but not OK (200) - double duration = durationInMilliseconds(this.requestStartTime, Instant.now()); - addResultsOnce(new QueryExecutionStats(this.queryId, COMMON.QUERY_SUCCESS, duration, 0)); - } else { // response status indicates that the query did not succeed (!= 2xx) - double duration = durationInMilliseconds(this.requestStartTime, Instant.now()); - addResultsOnce(new QueryExecutionStats(this.queryId, COMMON.QUERY_HTTP_FAILURE, duration)); - } - } - - abstract void buildRequest(String query, String queryID) throws UnsupportedEncodingException; - - protected void initClient() { - this.client = HttpClients.custom().setConnectionManager(new BasicHttpClientConnectionManager()).build(); - } - - protected void closeClient() { - closeResponse(); - try { - if (this.client != null) - this.client.close(); - } catch (IOException e) { - LOGGER.error("Could not close http response ", e); - } - this.client = null; - } - - protected void closeResponse() { - try { - if (this.response != null) - this.response.close(); - } catch (IOException e) { - LOGGER.error("Could not close Client ", e); - } - this.response = null; - } - - /** - * Http Result Processor, analyzes the http response in the background, if it was cached already, what is the result size, - * did the response was a success or failure. - */ - static class HttpResultProcessor implements Runnable { - - private final Logger LOGGER = LoggerFactory.getLogger(getClass()); - - private final HttpWorker httpWorker; - private final String queryId; - private final double duration; - private final Header contentTypeHeader; - private final long contentLength; - private ByteArrayOutputStream contentStream; - - public HttpResultProcessor(HttpWorker httpWorker, String queryId, double duration, Header contentTypeHeader, ByteArrayOutputStream contentStream, long contentLength) { - this.httpWorker = httpWorker; - this.queryId = queryId; - this.duration = duration; - this.contentTypeHeader = contentTypeHeader; - this.contentStream = contentStream; - this.contentLength = contentLength; - } - - @Override - public void run() { - // Result size is not saved before. Process the http response. - - ConcurrentMap processedResults = this.httpWorker.getProcessedResults(); - QueryResultHashKey resultCacheKey = new QueryResultHashKey(this.queryId, this.contentLength); - try { - //String content = contentStream.toString(StandardCharsets.UTF_8.name()); - //contentStream = null; // might be hugh, dereference immediately after consumed - Long resultSize = this.httpWorker.queryHandler.getLanguageProcessor().getResultSize(this.contentTypeHeader, this.contentStream, this.contentLength); - this.contentStream = null; - // Save the result size to be re-used - processedResults.put(resultCacheKey, resultSize); - LOGGER.debug("added Result Cache Key {}", resultCacheKey); - - this.httpWorker.addResults(new QueryExecutionStats(this.queryId, COMMON.QUERY_SUCCESS, this.duration, resultSize)); - - } catch (IOException | ParseException | ParserConfigurationException | SAXException e) { - LOGGER.error("Query results could not be parsed. ", e); - this.httpWorker.addResults(new QueryExecutionStats(this.queryId, COMMON.QUERY_UNKNOWN_EXCEPTION, this.duration)); - } catch (Exception e) { - e.printStackTrace(); - } - } - } -} - diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/MultipleCLIInputWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/MultipleCLIInputWorker.java deleted file mode 100644 index 355aa3767..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/MultipleCLIInputWorker.java +++ /dev/null @@ -1,180 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.utils.CLIProcessManager; -import org.aksw.iguana.cc.worker.AbstractWorker; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; -import org.aksw.iguana.commons.constants.COMMON; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -import static org.aksw.iguana.commons.time.TimeUtils.durationInMilliseconds; - -/** - * Worker to execute a query against a CLI process, the connection.service will be the command to execute the query against. - *

- * Assumes that the CLI process won't stop but will just accepts queries one after another and returns the results in the CLI output. - *

- * This worker can be set to be created multiple times in the background if one process will throw an error, a backup process was already created and can be used. - * This is handy if the process won't just prints an error message, but simply exits. - */ -@Shorthand("MultipleCLIInputWorker") -public class MultipleCLIInputWorker extends AbstractWorker { - - private final Logger LOGGER = LoggerFactory.getLogger(getClass()); - private final String initFinished; - private final String queryFinished; - private final String error; - protected List processList; - protected int currentProcessId = 0; - protected int numberOfProcesses = 5; - private Process currentProcess; - - public MultipleCLIInputWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, String initFinished, String queryFinished, String queryError, @Nullable Integer numberOfProcesses) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency); - this.initFinished = initFinished; - this.queryFinished = queryFinished; - this.error = queryError; - if (numberOfProcesses != null) { - this.numberOfProcesses = numberOfProcesses; - } - this.setWorkerProperties(); - } - - private void setWorkerProperties() { - // start cli input - - // Create processes, set first process as current process - this.processList = CLIProcessManager.createProcesses(this.numberOfProcesses, this.con.getEndpoint()); - this.currentProcess = this.processList.get(0); - - // Make sure that initialization is complete - for (Process value : this.processList) { - try { - CLIProcessManager.countLinesUntilStringOccurs(value, initFinished, error); - } catch (IOException e) { - LOGGER.error("Exception while trying to wait for init of CLI Process", e); - } - } - } - - - @Override - public void executeQuery(String query, String queryID) { - Instant start = Instant.now(); - // execute queryCLI and read response - try { - // Create background thread that will watch the output of the process and prepare results - AtomicLong size = new AtomicLong(-1); - AtomicBoolean failed = new AtomicBoolean(false); - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.execute(() -> { - try { - LOGGER.debug("Process Alive: {}", this.currentProcess.isAlive()); - LOGGER.debug("Reader ready: {}", CLIProcessManager.isReaderReady(this.currentProcess)); - size.set(CLIProcessManager.countLinesUntilStringOccurs(this.currentProcess, this.queryFinished, this.error)); - } catch (IOException e) { - failed.set(true); - } - }); - - // Execute the query on the process - try { - if (this.currentProcess.isAlive()) { - CLIProcessManager.executeCommand(this.currentProcess, writableQuery(query)); - } else if (this.endSignal) { - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - return; - } else { - setNextProcess(); - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - return; - } - } finally { - executor.shutdown(); - executor.awaitTermination((long) (double) this.timeOut, TimeUnit.MILLISECONDS); - } - - // At this point, query is executed and background thread has processed the results. - // Next, calculate time for benchmark. - double duration = durationInMilliseconds(start, Instant.now()); - - if (duration >= this.timeOut) { - setNextProcess(); - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_SOCKET_TIMEOUT, duration)); - return; - } else if (failed.get()) { - if (!this.currentProcess.isAlive()) { - setNextProcess(); - } - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, duration)); - return; - } - - // SUCCESS - LOGGER.debug("Query successfully executed size: {}", size.get()); - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_SUCCESS, duration, size.get())); - } catch (IOException | InterruptedException e) { - LOGGER.warn("Exception while executing query ", e); - // ERROR - super.addResults(new QueryExecutionStats(queryID, COMMON.QUERY_UNKNOWN_EXCEPTION, durationInMilliseconds(start, Instant.now()))); - } - } - - private void setNextProcess() { - int oldProcessId = this.currentProcessId; - this.currentProcessId = this.currentProcessId == this.processList.size() - 1 ? 0 : this.currentProcessId + 1; - - // destroy old process - CLIProcessManager.destroyProcess(this.currentProcess); - if (oldProcessId == this.currentProcessId) { - try { - this.currentProcess.waitFor(); - } catch (InterruptedException e) { - LOGGER.error("Process was Interrupted", e); - } - } - - // create and initialize new process to replace previously destroyed process - Process replacementProcess = CLIProcessManager.createProcess(this.con.getEndpoint()); - try { - CLIProcessManager.countLinesUntilStringOccurs(replacementProcess, this.initFinished, this.error); // Init - this.processList.set(oldProcessId, replacementProcess); - } catch (IOException e) { - LOGGER.error("Process replacement didn't work", e); - } - - // finally, update current process - this.currentProcess = this.processList.get(this.currentProcessId); - } - - protected String writableQuery(String query) { - return query; - } - - - @Override - public void stopSending() { - super.stopSending(); - for (Process pr : this.processList) { - pr.destroyForcibly(); - try { - pr.waitFor(); - } catch (InterruptedException e) { - LOGGER.error("Process waitFor was Interrupted", e); - } - } - } -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/SPARQLProtocolWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/SPARQLProtocolWorker.java new file mode 100644 index 000000000..4d43350b2 --- /dev/null +++ b/src/main/java/org/aksw/iguana/cc/worker/impl/SPARQLProtocolWorker.java @@ -0,0 +1,445 @@ +package org.aksw.iguana.cc.worker.impl; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import net.jpountz.xxhash.XXHashFactory; +import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import org.aksw.iguana.cc.query.handler.QueryHandler; +import org.aksw.iguana.cc.worker.ResponseBodyProcessor; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.commons.io.BigByteArrayOutputStream; +import org.apache.http.client.utils.URIBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.helpers.MessageFormatter; + +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class SPARQLProtocolWorker extends HttpWorker { + + public final static class RequestFactory { + public enum RequestType { + GET_QUERY("get query"), + POST_URL_ENC_QUERY("post url-enc query"), + POST_QUERY("post query"), + POST_URL_ENC_UPDATE("post url-enc update"), + POST_UPDATE("post update"); + + private final String value; + + @JsonCreator + RequestType(String value) { + this.value = Objects.requireNonNullElse(value, "get query"); + } + + @JsonValue + public String value() { + return value; + } + } + + private final RequestType requestType; + + public RequestFactory(RequestType requestType) { + this.requestType = requestType; + } + + private static String urlEncode(List parameters) { + return parameters.stream() + .map(e -> e[0] + "=" + URLEncoder.encode(e[1], StandardCharsets.UTF_8)) + .collect(Collectors.joining("&")); + } + + public HttpRequest buildHttpRequest(InputStream queryStream, + Duration timeout, + ConnectionConfig connection, + String requestHeader) throws URISyntaxException, IOException { + HttpRequest.Builder request = HttpRequest.newBuilder().timeout(timeout); + + class CustomStreamSupplier { + boolean used = false; // assume, that the stream will only be used again, if the first request failed, because of the client + public Supplier getStreamSupplier() { + if (!used) { + used = true; + return () -> queryStream; + } + else + return () -> null; + } + } + + if (requestHeader != null) + request.header("Accept", requestHeader); + if (connection.authentication() != null && connection.authentication().user() != null) + request.header("Authorization", + HttpWorker.basicAuth(connection.authentication().user(), + Optional.ofNullable(connection.authentication().password()).orElse(""))); + switch (this.requestType) { + case GET_QUERY -> { + request.uri(new URIBuilder(connection.endpoint()) + .setParameter("query", + new String(queryStream.readAllBytes(), StandardCharsets.UTF_8)) + .build()) + .GET(); + } + case POST_URL_ENC_QUERY -> { + request.uri(connection.endpoint()) + .header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.ofString( + urlEncode(Collections.singletonList( + new String[]{"query" /* query is already URL encoded */, + new String(queryStream.readAllBytes(), StandardCharsets.UTF_8)})))); + } + case POST_QUERY -> { + request.uri(connection.endpoint()) + .header("Content-Type", "application/sparql-query") + .POST(HttpRequest.BodyPublishers.ofInputStream(new CustomStreamSupplier().getStreamSupplier())); + } + case POST_URL_ENC_UPDATE -> { + request.uri(connection.endpoint()) + .header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.ofString( + urlEncode(Collections.singletonList( + new String[]{"update" /* query is already URL encoded */, + new String(queryStream.readAllBytes(), StandardCharsets.UTF_8)})))); + } + case POST_UPDATE -> { + request.uri(connection.endpoint()) + .header("Content-Type", "application/sparql-update") + .POST(HttpRequest.BodyPublishers.ofInputStream(new CustomStreamSupplier().getStreamSupplier())); + } + } + return request.build(); + } + } + + + public record Config( + Integer number, + QueryHandler queries, + CompletionTarget completionTarget, + ConnectionConfig connection, + Duration timeout, + String acceptHeader /* e.g. application/sparql-results+json */, + RequestFactory.RequestType requestType, + Boolean parseResults + ) implements HttpWorker.Config { + public Config(Integer number, + @JsonProperty(required = true) QueryHandler queries, + @JsonProperty(required = true) CompletionTarget completionTarget, + @JsonProperty(required = true) ConnectionConfig connection, + @JsonProperty(required = true) Duration timeout, + String acceptHeader, + RequestFactory.RequestType requestType, + Boolean parseResults) { + this.number = number == null ? 1 : number; + this.queries = queries; + this.completionTarget = completionTarget; + this.connection = connection; + this.timeout = timeout; + this.acceptHeader = acceptHeader; + this.requestType = requestType == null ? RequestFactory.RequestType.GET_QUERY : requestType; + this.parseResults = parseResults == null || parseResults; + } + } + + record HttpExecutionResult( + int queryID, + Optional> response, + Instant requestStart, + Duration duration, + Optional outputStream, + OptionalLong actualContentLength, + OptionalLong hash, + Optional exception + ) { + public boolean completed() { + return response.isPresent(); + } + + public boolean successful() { + if (response.isPresent() && exception.isEmpty()) + return (response.get().statusCode() / 100) == 2; + return false; + } + } + + + private HttpClient httpClient; + private final ThreadPoolExecutor executor; + + private final XXHashFactory hasherFactory = XXHashFactory.fastestJavaInstance(); + private final RequestFactory requestFactory; + + private final ResponseBodyProcessor responseBodyProcessor; + + // declared here, so it can be reused across multiple method calls + private BigByteArrayOutputStream responseBodybbaos = new BigByteArrayOutputStream(); + + // used to read the http response body + private final byte[] buffer = new byte[4096]; + + private final static Logger LOGGER = LoggerFactory.getLogger(SPARQLProtocolWorker.class); + + @Override + public Config config() { + return (SPARQLProtocolWorker.Config) config; + } + + + public SPARQLProtocolWorker(long workerId, ResponseBodyProcessor responseBodyProcessor, Config config) { + super(workerId, responseBodyProcessor, config); + this.responseBodyProcessor = responseBodyProcessor; + this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); + this.requestFactory = new RequestFactory(config().requestType()); + this.httpClient = buildHttpClient(); + } + + /** + * Starts the worker and returns a CompletableFuture, which will be completed, when the worker has finished the + * completion target. The CompletableFuture will contain a Result object, which contains the execution stats of the + * worker. The execution stats contain the execution time, the http status code, the content length and the hash of + * the response body. If the worker failed to execute a query, the execution stats will contain an exception. + * If the worker failed to execute a query, because of a set time limit in the worker configuration, the result + * of that execution will be discarded. + * + * @return the CompletableFuture the contains the results of the worker. + */ + public CompletableFuture start() { + return CompletableFuture.supplyAsync(() -> { + ZonedDateTime startTime = ZonedDateTime.now(); + List executionStats = new ArrayList<>(); + if (config().completionTarget() instanceof QueryMixes queryMixes) { + for (int i = 0; i < queryMixes.number(); i++) { + for (int j = 0; j < config().queries().getQueryCount(); j++) { + ExecutionStats execution = executeQuery(config().timeout(), false); + if (execution == null) throw new RuntimeException("Execution returned null at a place, where it should have never been null."); + logExecution(execution); + executionStats.add(execution); + } + LOGGER.info("{}\t:: Completed {} out of {} querymixes", this, i + 1, queryMixes.number()); + } + } else if (config().completionTarget() instanceof TimeLimit timeLimit) { + final Instant endTime = Instant.now().plus(timeLimit.duration()); + Instant now; + while ((now = Instant.now()).isBefore(endTime)) { + final Duration timeToEnd = Duration.between(now, endTime); + final boolean reducedTimeout = config().timeout().compareTo(timeToEnd) > 0; + final Duration thisQueryTimeOut = (reducedTimeout) ? timeToEnd : config().timeout(); + ExecutionStats execution = executeQuery(thisQueryTimeOut, reducedTimeout); + if (execution != null){ // If timeout is reduced, the execution result might be discarded if it failed and executeQuery returns null. + logExecution(execution); + executionStats.add(execution); + } + } + LOGGER.info("{}\t:: Reached time limit of {}.", this, timeLimit.duration()); + } + ZonedDateTime endTime = ZonedDateTime.now(); + return new Result(this.workerID, executionStats, startTime, endTime); + }, executor); + } + + /** + * Executes the next query given by the query selector from the query handler. If the execution fails and + * discardOnFailure is true, the execution will be discarded and null will be returned. If the execution fails and + * discardOnFailure is false, the execution statistic with the failed results will be returned. + * + * @param timeout the timeout for the execution + * @param discardOnFailure if true, this method will return null, if the execution fails + * @return the execution statistic of the execution + */ + private ExecutionStats executeQuery(Duration timeout, boolean discardOnFailure) { + HttpExecutionResult result = executeHttpRequest(timeout); + Optional statuscode = Optional.empty(); + if (result.response().isPresent()) + statuscode = Optional.of(result.response().get().statusCode()); + + if (result.successful() && this.config.parseResults()) { // 2xx + if (result.actualContentLength.isEmpty() || result.hash.isEmpty() || result.outputStream.isEmpty()) { + throw new RuntimeException("Response body is null, but execution was successful."); // This should never happen + } + + // process result + if (!responseBodyProcessor.add(result.actualContentLength().orElse(-1), result.hash().orElse(-1), result.outputStream().orElse(new BigByteArrayOutputStream()))) { + this.responseBodybbaos = result.outputStream().orElse(new BigByteArrayOutputStream()); + } else { + this.responseBodybbaos = new BigByteArrayOutputStream(); + } + } + + try { + this.responseBodybbaos.reset(); + } catch (IOException e) { + this.responseBodybbaos = new BigByteArrayOutputStream(); + } + + // This is not explicitly checking for a timeout, instead it just checks if the execution was successful or not. + // TODO: This might cause problems if the query actually fails before the timeout and discardOnFailure is true. + if (!result.successful() && discardOnFailure) { + LOGGER.debug("{}\t:: Discarded execution, because the time limit has been reached: [queryID={}]", this, result.queryID); + return null; + } + + return new ExecutionStats( + result.queryID(), + result.requestStart(), + result.duration(), + statuscode, + result.actualContentLength(), + result.hash, + result.exception() + ); + } + + + private HttpExecutionResult executeHttpRequest(Duration timeout) { + final QueryHandler.QueryStreamWrapper queryHandle; + try { + queryHandle = config().queries().getNextQueryStream(this.querySelector); + } catch (IOException e) { + return new HttpExecutionResult( + this.querySelector.getCurrentIndex(), + Optional.empty(), + Instant.now(), + Duration.ZERO, + Optional.empty(), + OptionalLong.empty(), + OptionalLong.empty(), + Optional.of(e) + ); + } + + final HttpRequest request; + + try { + request = requestFactory.buildHttpRequest( + queryHandle.queryInputStream(), + timeout, + config().connection(), + config().acceptHeader() + ); + } catch (IOException | URISyntaxException e) { + return new HttpExecutionResult( + queryHandle.index(), + Optional.empty(), + Instant.now(), + Duration.ZERO, + Optional.empty(), + OptionalLong.empty(), + OptionalLong.empty(), + Optional.of(e) + ); + } + + // check if the last execution task is stuck + if (this.httpClient.executor().isPresent() && ((ThreadPoolExecutor) this.httpClient.executor().get()).getActiveCount() != 0) { + // This might never cancel the task if the client that's connected to is broken. There also seems to be a + // bug where the httpClient never properly handles the interrupt from the shutdownNow method. + // See: https://bugs.openjdk.org/browse/JDK-8294047 + ((ThreadPoolExecutor) this.httpClient.executor().get()).shutdownNow(); + final var waitStart = Instant.now(); + try { + while (!((ThreadPoolExecutor) this.httpClient.executor().get()).awaitTermination(1, TimeUnit.SECONDS)) { + LOGGER.warn("{}\t:: [Thread-ID: {}]\t:: Waiting for the http client to shutdown. Elapsed time: {}", this, Thread.currentThread().getId(), Duration.between(waitStart, Instant.now())); + } + } catch (InterruptedException ignored) { + LOGGER.warn("{}\t:: Http client never shutdown. Continuing with the creation of a new http client.", this); + } + this.httpClient = buildHttpClient(); + } + + final Instant timeStamp = Instant.now(); + final var requestStart = System.nanoTime(); + BiFunction, Exception, HttpExecutionResult> createFailedResult = (response, e) -> new HttpExecutionResult( + queryHandle.index(), + Optional.ofNullable(response), + timeStamp, + Duration.ofNanos(System.nanoTime() - requestStart), + Optional.empty(), + OptionalLong.empty(), + OptionalLong.empty(), + Optional.ofNullable(e) + ); + + try { + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(httpResponse -> { + try (final var bodyStream = httpResponse.body()) { + if (httpResponse.statusCode() / 100 == 2) { // Request was successful + OptionalLong contentLength = httpResponse.headers().firstValueAsLong("Content-Length"); + try (var hasher = hasherFactory.newStreamingHash64(0)) { + int readBytes; + while ((readBytes = bodyStream.readNBytes(this.buffer, 0, this.buffer.length)) != 0) { + if (Duration.between(Instant.now(), timeStamp.plus(timeout)).isNegative()) { + return createFailedResult.apply(httpResponse, new TimeoutException()); + } + hasher.update(this.buffer, 0, readBytes); + this.responseBodybbaos.write(this.buffer, 0, readBytes); + } + + if (contentLength.isPresent() && + (this.responseBodybbaos.size() < contentLength.getAsLong() || + this.responseBodybbaos.size() > contentLength.getAsLong())) { + return createFailedResult.apply(httpResponse, new ProtocolException("Content-Length header value doesn't match actual content length.")); + } + + return new HttpExecutionResult( + queryHandle.index(), + Optional.of(httpResponse), + timeStamp, + Duration.ofNanos(System.nanoTime() - requestStart), + Optional.of(this.responseBodybbaos), + OptionalLong.of(this.responseBodybbaos.size()), + OptionalLong.of(hasher.getValue()), + Optional.empty() + ); + } + } else { + return createFailedResult.apply(httpResponse, null); + } + } catch (IOException ex) { + return createFailedResult.apply(httpResponse, ex); + } + }).get(timeout.toNanos(), TimeUnit.NANOSECONDS); + } catch (CompletionException | InterruptedException | ExecutionException | TimeoutException e) { + return createFailedResult.apply(null, e); + } + } + + private HttpClient buildHttpClient() { + return HttpClient.newBuilder() + .executor(Executors.newFixedThreadPool(1)) + .followRedirects(HttpClient.Redirect.ALWAYS) + .connectTimeout(config().timeout()) + .build(); + } + + private void logExecution(ExecutionStats execution) { + switch (execution.endState()) { + case SUCCESS -> LOGGER.debug("{}\t:: Successfully executed query: [queryID={}].", this, execution.queryID()); + case TIMEOUT -> LOGGER.warn("{}\t:: Timeout during query execution: [queryID={}, duration={}].", this, execution.queryID(), execution.duration()); // TODO: look for a possibility to add the query string for better logging + case HTTP_ERROR -> LOGGER.warn("{}\t:: HTTP Error occurred during query execution: [queryID={}, httpError={}].", this, execution.queryID(), execution.httpStatusCode().orElse(-1)); + case MISCELLANEOUS_EXCEPTION -> LOGGER.warn("{}\t:: Miscellaneous exception occurred during query execution: [queryID={}, exception={}].", this, execution.queryID(), execution.error().orElse(null)); + } + } + + @Override + public String toString() { + return MessageFormatter.format("[{}-{}]", SPARQLProtocolWorker.class.getSimpleName(), this.workerID).getMessage(); + } +} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/UPDATEWorker.java b/src/main/java/org/aksw/iguana/cc/worker/impl/UPDATEWorker.java deleted file mode 100644 index ece958b67..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/UPDATEWorker.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.worker.impl.update.UpdateTimer; -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.Shorthand; - -import java.io.IOException; -import java.time.Instant; -import java.util.Map; - -import static org.aksw.iguana.commons.time.TimeUtils.durationInMilliseconds; - -/** - * A Worker using SPARQL Updates to create service request. - * - * @author f.conrads - */ -@Shorthand("UPDATEWorker") -public class UPDATEWorker extends HttpPostWorker { - private final String timerStrategy; - private UpdateTimer updateTimer = new UpdateTimer(); - - private int queryCount; - - public UPDATEWorker(String taskID, Integer workerID, ConnectionConfig connection, Map queries, @Nullable Integer timeLimit, @Nullable Integer timeOut, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, @Nullable String timerStrategy) { - super(taskID, workerID, connection, queries, timeLimit, timeOut, fixedLatency, gaussianLatency, null, null, "application/sparql-update"); - this.timerStrategy = timerStrategy; - } - - @Override - public void startWorker() { - setUpdateTimer(this.timerStrategy); - super.startWorker(); - } - - @Override - public void waitTimeMs() { - double wait = this.updateTimer.calculateTime(durationInMilliseconds(this.startTime, Instant.now()), this.tmpExecutedQueries); - LOGGER.debug("Worker[{{}} : {{}}]: Time to wait for next Query {{}}", this.workerType, this.workerID, wait); - try { - Thread.sleep((long) wait); - } catch (InterruptedException e) { - LOGGER.error("Worker[{{}} : {{}}]: Could not wait time before next query due to: {{}}", this.workerType, this.workerID, e); - LOGGER.error("", e); - } - super.waitTimeMs(); - } - - @Override - public synchronized void addResults(QueryExecutionStats result) { - this.results.add(result); - this.executedQueries++; - } - - @Override - public void getNextQuery(StringBuilder queryStr, StringBuilder queryID) throws IOException { - // If there is no more update send end signal, as there is nothing to do anymore - if (this.queryCount >= this.queryHandler.getQueryCount()) { - stopSending(); - return; - } - - this.queryHandler.getNextQuery(queryStr, queryID); - this.queryCount++; - } - - /** - * Sets Update Timer according to strategy - * - * @param strategyStr The String representation of a UpdateTimer.Strategy - */ - private void setUpdateTimer(String strategyStr) { - if (strategyStr == null) return; - UpdateTimer.Strategy strategy = UpdateTimer.Strategy.valueOf(strategyStr.toUpperCase()); - switch (strategy) { - case FIXED: - if (this.timeLimit != null) { - this.updateTimer = new UpdateTimer(this.timeLimit / this.queryHandler.getQueryCount()); - } else { - LOGGER.warn("Worker[{{}} : {{}}]: FIXED Updates can only be used with timeLimit!", this.workerType, this.workerID); - } - break; - case DISTRIBUTED: - if (this.timeLimit != null) { - this.updateTimer = new UpdateTimer(this.queryHandler.getQueryCount(), this.timeLimit); - } else { - LOGGER.warn("Worker[{{}} : {{}}]: DISTRIBUTED Updates can only be used with timeLimit!", this.workerType, this.workerID); - } - break; - default: - break; - } - LOGGER.debug("Worker[{{}} : {{}}]: UpdateTimer was set to UpdateTimer:{{}}", this.workerType, this.workerID, this.updateTimer); - } - - - /** - * Checks if one queryMix was already executed, as it does not matter how many mixes should be executed - * - * @param noOfQueryMixes - * @return - */ - @Override - public boolean hasExecutedNoOfQueryMixes(Long noOfQueryMixes) { - return getExecutedQueries() / (getNoOfQueries() * 1.0) >= 1; - } - -} diff --git a/src/main/java/org/aksw/iguana/cc/worker/impl/update/UpdateTimer.java b/src/main/java/org/aksw/iguana/cc/worker/impl/update/UpdateTimer.java deleted file mode 100644 index f1660baa1..000000000 --- a/src/main/java/org/aksw/iguana/cc/worker/impl/update/UpdateTimer.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.aksw.iguana.cc.worker.impl.update; - -/** - * - * Class to calculate time between two update queries. - * - * @author f.conrads - * - */ -public class UpdateTimer { - - private Strategy strategy; - private double baseValue; - private Double timeLimit; - - - /** - * - * The possible strategies - *

    - *
  • NONE: updates will be executed immediately after another
  • - *
  • FIXED: a fixed value in ms will be waited before the next update query
  • - *
  • DISTRIBUTED: the updates will be equally distributed over the time limit of the task
  • - *
- * - * @author f.conrads - * - */ - public enum Strategy { - /** - * updates will be executed immediately after another - */ - NONE, - - /** - * a fixed value in ms will be waited before the next update query - */ - FIXED, - - /** - * the updates will be equally distributed over the time limit of the task - */ - DISTRIBUTED - } - - /** - * Creates the default UpdateTimer - * All update queries will be executed immediately after another - */ - public UpdateTimer() { - this.strategy= Strategy.NONE; - } - - /** - * Creates a FixedUpdateTimer - * - * @param fixedValue the fixed time to wait between queries - */ - public UpdateTimer(double fixedValue) { - this.strategy= Strategy.FIXED; - this.baseValue=fixedValue; - } - - /** - * Creates a distributed UpdateTimer - * - * @param noOfUpdates the number of update queries - * @param timeLimit the timeLimit of the task - */ - public UpdateTimer(int noOfUpdates, Double timeLimit) { - this.strategy= Strategy.DISTRIBUTED; - this.baseValue=noOfUpdates; - this.timeLimit = timeLimit; - } - - - /** - * calculates the time the UPDATEWorker has to wait until the next update query - * - * @param timeExceeded The time it took from start of the task to now - * @param executedQueries currently number of executed Update Queries - * @return The time to wait - */ - public double calculateTime(double timeExceeded, long executedQueries) { - switch(strategy) { - case FIXED: - return baseValue; - case DISTRIBUTED: - return (timeLimit-timeExceeded)/(baseValue-executedQueries); - default: - return 0; - } - } - - - @Override - public String toString() { - return "[strategy: "+this.strategy.name()+"]"; - } -} \ No newline at end of file diff --git a/src/main/java/org/aksw/iguana/commons/annotation/Nullable.java b/src/main/java/org/aksw/iguana/commons/annotation/Nullable.java deleted file mode 100644 index 3d11e9dd7..000000000 --- a/src/main/java/org/aksw/iguana/commons/annotation/Nullable.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.aksw.iguana.commons.annotation; - -import java.lang.annotation.*; - -/** - * Lets the TypeFactory know that the Parameter can be null and thus be ignored. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PARAMETER) -public @interface Nullable { -} diff --git a/src/main/java/org/aksw/iguana/commons/annotation/ParameterNames.java b/src/main/java/org/aksw/iguana/commons/annotation/ParameterNames.java deleted file mode 100644 index ceae9f810..000000000 --- a/src/main/java/org/aksw/iguana/commons/annotation/ParameterNames.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.aksw.iguana.commons.annotation; - -import java.lang.annotation.*; - -/** - * Uses provided names in the order of the constructor parameters, instead of the constructor parameter names for the TypeFactory - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.CONSTRUCTOR) -@Inherited -public @interface ParameterNames { - - String[] names() default ""; -} diff --git a/src/main/java/org/aksw/iguana/commons/annotation/Shorthand.java b/src/main/java/org/aksw/iguana/commons/annotation/Shorthand.java deleted file mode 100644 index ee19817ca..000000000 --- a/src/main/java/org/aksw/iguana/commons/annotation/Shorthand.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.aksw.iguana.commons.annotation; - -import java.lang.annotation.*; - -/** - * Sets a short name to be used in the TypedFactory instead of the whole class name - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Shorthand { - - String value(); -} diff --git a/src/main/java/org/aksw/iguana/commons/constants/COMMON.java b/src/main/java/org/aksw/iguana/commons/constants/COMMON.java deleted file mode 100644 index 1a63bc9bf..000000000 --- a/src/main/java/org/aksw/iguana/commons/constants/COMMON.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.aksw.iguana.commons.constants; - -/** - * Constants several modules need - * - * @author f.conrads - * - */ -public class COMMON { - - /* - * COMMON CONSTANTS - */ - - - /** - * The key for the experiment task ID in the properties received from the core - */ - public static final String EXPERIMENT_TASK_ID_KEY = "taskID"; - - /** - * The key for the experiment ID in the properties received from the core - */ - public static final String EXPERIMENT_ID_KEY = "expID"; - - /** - * The key for suite ID in the properties received from the core - */ - public static final String SUITE_ID_KEY = "suiteID"; - - - /** - * The key for starting an experiment task. Must be in the receiving properties - */ - public static final String RECEIVE_DATA_START_KEY = "startExperimentTask"; - - /** - * The key for ending an experiment task. Must be in the receiving properties - */ - public static final String RECEIVE_DATA_END_KEY = "endExperimentTask"; - - - /** - * Key in the properties receiving from the core to start an experiment - * as well as internal rp metrics key - */ - public static final String METRICS_PROPERTIES_KEY = "metrics"; - - - - /** - * TP2RP query time key - */ - public static final String RECEIVE_DATA_TIME = "resultTime"; - - /** - * TP2RP (Controller2RP) query success key - */ - public static final String RECEIVE_DATA_SUCCESS = "resultSuccess"; - - /** - * The number of Queries in the particular experiment - * will be used in the meta data. - */ - public static final String NO_OF_QUERIES = "noOfQueries"; - - - - public static final String QUERY_ID_KEY = "queryID"; - - public static final String CONNECTION_ID_KEY = "connID"; - - public static final String DATASET_ID_KEY = "datasetID"; - - public static final String EXTRA_META_KEY = "extraMeta"; - - public static final String EXTRA_IS_RESOURCE_KEY = "setIsResource"; - - public static final String QUERY_STRING = "queryString"; - - public static final String DOUBLE_RAW_RESULTS = "doubleRawResults"; - - public static final String SIMPLE_TRIPLE_KEY = "cleanTripleText"; - - public static final String QUERY_STATS = "queryStats"; - - public static final Object RECEIVE_DATA_SIZE = "resultSize"; - - public static final String QUERY_HASH = "queryHash"; - - public static final String WORKER_ID = "workerID"; - - /* Various status codes to denote the status of query execution and to prepare QueryExecutionStats object */ - public static final long QUERY_UNKNOWN_EXCEPTION = 0L; - - public static final long QUERY_SUCCESS = 1L; - - public static final long QUERY_SOCKET_TIMEOUT = -1L; - - public static final long QUERY_HTTP_FAILURE = -2L; - - public static final String EXPERIMENT_TASK_CLASS_ID_KEY = "actualTaskClass" ; - - public static final String BASE_URI = "http://iguana-benchmark.eu"; - - - public static final String RES_BASE_URI = BASE_URI+"/resource/"; - public static final String PROP_BASE_URI = BASE_URI+"/properties/"; - public static final String CLASS_BASE_URI = BASE_URI+"/class/"; - public static final String PENALTY = "penalty"; - public static final String CONNECTION_VERSION_KEY = "connectionVersion"; - public static final String EXPERIMENT_TASK_NAME_KEY = "taskName"; -} diff --git a/src/main/java/org/aksw/iguana/commons/factory/TypedFactory.java b/src/main/java/org/aksw/iguana/commons/factory/TypedFactory.java deleted file mode 100644 index 4385f8a52..000000000 --- a/src/main/java/org/aksw/iguana/commons/factory/TypedFactory.java +++ /dev/null @@ -1,293 +0,0 @@ -/** - * - */ -package org.aksw.iguana.commons.factory; - -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.ParameterNames; -import org.aksw.iguana.commons.reflect.ShorthandMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Parameter; -import java.util.*; - - -/** - * Factory for a Type. - * Creates an Object from Constructor and Constructor Arguments - * - * @author f.conrads - * @param The Type which should be created - * - */ -public class TypedFactory { - - private static final Logger LOGGER = LoggerFactory - .getLogger(TypedFactory.class); - - private String getClassName(String className){ - Map map = ShorthandMapper.getInstance().getShortMap(); - if(map.containsKey(className)){ - return map.get(className); - } - return className; - } - - - /** - * Will create a T Object from a Constructor Object created by the - * class name and the constructor arguments, be aware that all arguments - * must be Strings in the constructor. - * - * @param className - * The Class Name of the Implemented T Object - * @param constructorArgs - * constructor arguments (must be Strings), can be safely null - * @return The T Object created by the Constructor using the - * constructor args - */ - @SuppressWarnings("unchecked") - public T create(String className, Object[] constructorArgs){ - Object[] constructorArgs2 = constructorArgs; - if (constructorArgs2 == null) { - constructorArgs2 = new Object[0]; - } - Class[] stringClass = new Class[constructorArgs2.length]; - Arrays.fill(stringClass, String.class); - return create(className, constructorArgs2, stringClass); - } - - /** - * Will create a T Object from a Constructor Object created by the - * class name and the constructor arguments, and an Array which states each - * Constructor Object Class - * - * @param className - * The Class Name of the Implemented T Object - * @param constructorArgs - * constructor arguments (must be Strings), can be safely null - * @param constructorClasses The class of each constructor argument - * @return The T Object created by the Constructor using the - * constructor args - */ - @SuppressWarnings("unchecked") - public T create(String className, Object[] constructorArgs, Class[] constructorClasses) { - - Object[] constructorArgs2 = constructorArgs; - - if (className == null) { - return null; - } - Class clazz; - try { - clazz = (Class) ClassLoader - .getSystemClassLoader().loadClass(className); - } catch (ClassNotFoundException e1) { - LOGGER.error("Could not load Object (name: " + className - + ")", e1); - return null; - } - - - if (constructorArgs2 == null) { - constructorArgs2 = new Object[0]; - } - if(constructorClasses==null){ - constructorClasses = new Class[constructorArgs2.length]; - Arrays.fill(constructorClasses, String.class); - } - - try { - Constructor constructor = clazz - .getConstructor(constructorClasses); - return constructor.newInstance(constructorArgs2); - } catch (InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - LOGGER.error("Could not initialize class " + clazz.getName() - + " with constructor.", e); - return null; - } - } - - - /** - * Uses the parameter Names and types of a constructor to find the best fitting constructor - * - * Only works with jvm -paramaters, otherwise use createAnnotated and annotate the constructors with ParameterNames and set names to the paramater names - * like - * . @ParameterNames(names={"a", "b"}) - * public Constructor(String a, Object b){...} - * - * @param className The Class Name of the Implemented T Object - * @param map key-value pair, whereas key represents the parameter name, where as value will be the value of the instantiation - * @return The instantiated object or null no constructor was found - */ - public T create(String className, Map map) { - Class clazz; - if (className == null) { - return null; - } - try { - clazz = (Class) ClassLoader.getSystemClassLoader().loadClass(getClassName(className)); - } catch (ClassNotFoundException e1) { - return null; - } - Constructor[] constructors = clazz.getConstructors(); - find: - for (Constructor constructor : constructors) { - //ParameterNames would be a backup - //ParameterNames paramNames = (ParameterNames) constructor.getAnnotation(ParameterNames.class); - //if(paramNames==null){ - // continue ; - //} - Parameter[] params = constructor.getParameters(); - - List names = new ArrayList<>(); - List> types = new ArrayList<>(); - Set canBeNull = new HashSet<>(); - for (Parameter p : params) { - names.add(p.getName()); - types.add(p.getType()); - if (p.isAnnotationPresent(Nullable.class)) { - canBeNull.add(p.getName()); - } - } - List instanceNames = new ArrayList<>(map.keySet()); - Object[] constructorArgs = new Object[names.size()]; - if (!checkIfFits(map, names, canBeNull)) { - continue; - } - for (String key : instanceNames) { - Object value = map.get(key); - //Check if constructor can map keys to param Names - int indexKey = names.indexOf(key); - Class clazz2 = types.get(indexKey); - if (!clazz2.isInstance(value)) { - continue find; - } - constructorArgs[indexKey] = value; - } - try { - return (T) constructor.newInstance(constructorArgs); - } catch (InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException - | SecurityException e) { - //As we check that the COnstructor fits this shouldn't be thrown at all. Something very bad happend - LOGGER.error("Could not initialize class " + clazz.getName() - + " with constructor.", e); - return null; - } - } - LOGGER.error("Could not initialize class " + clazz.getName() - + " with constructor. Maybe Config file has wrong names?."); - return null; - } - - /** - * Checks if the giving parameter key-value mapping fits the constructor parameter names (key vs names) and takes into account that the parameter is allowed to be null and thus - * can be disregarded - * - * @param map paramater - Object Map - * @param names parameter names of the actual constructor - * @param canBeNull all paramaters who can be null - * @return true if constructor fits, otherwise false - */ - private boolean checkIfFits(Map map, List names, Set canBeNull) { - //check if all provided parameter names are in the constructor - for (String key : map.keySet()) { - if (!names.contains(key)) { - return false; - } - } - //check if all notNull objects are provided - Set keySet = map.keySet(); - for (String name : names) { - //we can safely assume that Object is string - if (!keySet.contains(name)) { - //check if parameter is Nullable - if (!canBeNull.contains(name)) { - return false; - } - } - } - return true; - } - - /** - * Uses the parameter Names and types of a constructor to find the best fitting constructor - * - * Uses the ParameterNames annotation of a constructor to get the parameter names - * - * like - * . @ParameterNames(names={"a", "b"}) - * public Constructor(String a, Object b){...} - * - * @param className The Class Name of the Implemented T Object - * @param map Parameter name - value mapping - * @return The instantiated object or null no constructor was found - */ - public T createAnnotated(String className, Map map) { - Class clazz; - try { - clazz = (Class) ClassLoader.getSystemClassLoader().loadClass(getClassName(className)); - } catch (ClassNotFoundException e1) { - return null; - } - Constructor[] constructors = clazz.getConstructors(); - find: - for (Constructor constructor : constructors) { - ParameterNames paramNames = constructor.getAnnotation(ParameterNames.class); - if (paramNames == null) { - continue; - } - Parameter[] params = constructor.getParameters(); - - List names = new ArrayList<>(); - List> types = new ArrayList<>(); - Set canBeNull = new HashSet<>(); - for (int i = 0; i < params.length; i++) { - Parameter p = params[i]; - names.add(paramNames.names()[i]); - types.add(p.getType()); - if (p.isAnnotationPresent(Nullable.class)) { - canBeNull.add(p.getName()); - } - } - List instanceNames = new ArrayList<>(map.keySet()); - Object[] constructorArgs = new Object[names.size()]; - if (!checkIfFits(map, names, canBeNull)) { - continue; - } - for (String key : instanceNames) { - Object value = map.get(key); - //Check if constructor can map keys to param Names - int indexKey = names.indexOf(key); - Class clazz2 = types.get(indexKey); - if (!clazz2.isInstance(value)) { - continue find; - } - constructorArgs[indexKey] = value; - } - try { - return (T) constructor.newInstance(constructorArgs); - } catch (InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException - | SecurityException e) { - //As we check that the Constructor fits this shouldn't be thrown at all. Something very bad happend - LOGGER.error("Could not initialize class " + clazz.getName() - + " with constructor.", e); - return null; - } - } - LOGGER.error("Could not initialize class " + clazz.getName() - + " with constructor. Maybe Config file has wrong names?."); - return null; - } - - -} - diff --git a/src/main/java/org/aksw/iguana/commons/io/BigByteArrayInputStream.java b/src/main/java/org/aksw/iguana/commons/io/BigByteArrayInputStream.java index 5c41dfedd..1559fd7f1 100644 --- a/src/main/java/org/aksw/iguana/commons/io/BigByteArrayInputStream.java +++ b/src/main/java/org/aksw/iguana/commons/io/BigByteArrayInputStream.java @@ -2,53 +2,162 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Objects; + +import static java.lang.Math.min; public class BigByteArrayInputStream extends InputStream { - private BigByteArrayOutputStream bbaos; + final private BigByteArrayOutputStream bbaos; + + private byte[] currentBuffer; + private int currentBufferSize = -1; + private int posInCurrentBuffer = 0; - private byte[] curArray; - private int curPos=0; - private int curPosInArray=0; + private boolean ended = true; public BigByteArrayInputStream(byte[] bytes) throws IOException { bbaos = new BigByteArrayOutputStream(); bbaos.write(bytes); - setNextArray(); + activateNextBuffer(); } - public BigByteArrayInputStream(BigByteArrayOutputStream bbaos){ + /** + * The given bbaos will be closed, when read from it. + * + * @param bbaos + */ + public BigByteArrayInputStream(BigByteArrayOutputStream bbaos) { this.bbaos = bbaos; - setNextArray(); + activateNextBuffer(); } - private void setNextArray(){ - curArray=bbaos.getBaos().get(curPos++).toByteArray(); - } @Override public int read() throws IOException { - if(eos()){ - return -1; - } - int ret; + this.bbaos.close(); - if(curPosInArray==2147483639){ - ret = curArray[curPosInArray]; - curPosInArray=0; - setNextArray(); - } - else{ - ret=curArray[curPosInArray++]; + if (ended) return -1; + final var ret = currentBuffer[posInCurrentBuffer++]; + if (availableBytes() == 0) + activateNextBuffer(); + return ret & 0xFF; // convert byte (-128...127) to (0...255) + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + this.bbaos.close(); + Objects.checkFromIndexSize(off, len, b.length); + + if (ended) return -1; + + final var copyLength1 = min(availableBytes(), len); + System.arraycopy(currentBuffer, posInCurrentBuffer, b, off, copyLength1); + posInCurrentBuffer += copyLength1; + off += copyLength1; + if (availableBytes() == 0) + activateNextBuffer(); + + // check if b is already filled up or if there is nothing left to read + if (copyLength1 == len || ended) return copyLength1; + + // there might be the rare case, where reading one additional baos might not be enough to fill the buffer, + // because there are different array size limitations across different JVMs + final var copyLength2 = min(availableBytes(), len - copyLength1); + System.arraycopy(currentBuffer, posInCurrentBuffer, b, off, copyLength2); + posInCurrentBuffer += copyLength2; + + if (availableBytes() == 0) + activateNextBuffer(); + + return copyLength1 + copyLength2; + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + this.bbaos.close(); + Objects.checkFromIndexSize(off, len, b.length); + + if (ended) return 0; + + final var copyLength1 = min(availableBytes(), len); + System.arraycopy(currentBuffer, posInCurrentBuffer, b, off, copyLength1); + posInCurrentBuffer += copyLength1; + off += copyLength1; + if (availableBytes() == 0) + activateNextBuffer(); + + // check if b is already filled up or if there is nothing left to read + if (copyLength1 == len || ended) return copyLength1; + + // there might be the rare case, where reading one additional baos might not be enough to fill the buffer, + // because there are different array size limitations across different JVMs + final var copyLength2 = min(availableBytes(), len - copyLength1); + System.arraycopy(currentBuffer, posInCurrentBuffer, b, off, copyLength2); + posInCurrentBuffer += copyLength2; + + if (availableBytes() == 0) + activateNextBuffer(); + + return copyLength1 + copyLength2; + } + + @Override + public byte[] readAllBytes() throws IOException { + throw new IOException("Reading all bytes from a BigByteArrayInputStream is prohibited because it might exceed the array capacity"); + } + + @Override + public long skip(long n) throws IOException { + if (n <= 0) return 0; + long skipped = 0; + while (skipped < n) { + long thisSkip = min(availableBytes(), n - skipped); + skipped += thisSkip; + posInCurrentBuffer += (int) thisSkip; // conversion to int is lossless, because skipped is at maximum INT_MAX big + if (availableBytes() == 0) + if (!activateNextBuffer()) + return skipped; } - return ret ; + return skipped; } - private boolean eos() { - //if the current Position is equal the length of the array, this is the last array in bbaos and the last element was already read - if(curArray.length==curPosInArray){ - return true; + /** + * Activate the next buffer the underlying BigByteArrayOutputStream. + * + * @return true if the next buffer was activated, false if there are no more buffers available + */ + private boolean activateNextBuffer() { + // check if another buffer is available + if (bbaos.getBaos().isEmpty()) { + currentBuffer = null; // release memory + currentBufferSize = 0; + posInCurrentBuffer = 0; + ended = true; + return false; } - return false; + + // activate next buffer + currentBuffer = bbaos.getBaos().get(0).getBuffer(); + currentBufferSize = bbaos.getBaos().get(0).size(); + posInCurrentBuffer = 0; + + // remove the current buffer from the list to save memory + bbaos.getBaos().remove(0); + + // check if the new buffer contains anything + if (currentBuffer.length == 0) + return ended = activateNextBuffer(); + ended = false; + return true; + } + + /** + * Returns the number of available bytes in the current buffer. + * + * @return the number of available bytes in the current buffer + */ + private int availableBytes() { + return currentBufferSize - posInCurrentBuffer; } } diff --git a/src/main/java/org/aksw/iguana/commons/io/BigByteArrayOutputStream.java b/src/main/java/org/aksw/iguana/commons/io/BigByteArrayOutputStream.java index 605131977..2085b4158 100644 --- a/src/main/java/org/aksw/iguana/commons/io/BigByteArrayOutputStream.java +++ b/src/main/java/org/aksw/iguana/commons/io/BigByteArrayOutputStream.java @@ -1,108 +1,204 @@ package org.aksw.iguana.commons.io; -import java.io.ByteArrayOutputStream; +import org.apache.hadoop.hbase.io.ByteArrayOutputStream; + import java.io.IOException; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; - +import java.util.Objects; +import java.util.stream.IntStream; + +/** + * This class represents a ByteArrayOutputStream that can hold a large amount of byte data. + * It is designed to overcome the limitations of the standard ByteArrayOutputStream, which + * has a fixed internal byte array and can run into out of memory errors when trying to write + * a large amount of data. + *

+ * The BigByteArrayOutputStream works by using an ArrayList of ByteArrayOutputStreams to store + * the byte data. When the current ByteArrayOutputStream fills up, a new one is created with the + * maximum array size (Integer.MAX_VALUE - 8) as its initial capacity and added to the list. + * Writing data to the stream involves writing to the current active ByteArrayOutputStream. When + * the stream is cleared, all the internal ByteArrayOutputStreams are cleared and a new one is + * added to the list. + */ public class BigByteArrayOutputStream extends OutputStream { - private List baos = new ArrayList(); + /** + * The maximum size limit for an array. This is no limit to the amount of bytes {@code BigByteArrayOutputStream} can consume. + */ + public final static int ARRAY_SIZE_LIMIT = Integer.MAX_VALUE - 8; + + /** + * Holds a list of ByteArrayOutputStream objects. + */ + private final List baosList; + + /** + * The index of a ByteArrayOutputStream in the List baosList. + */ + private int baosListIndex; + /** + * Represents the current ByteArrayOutputStream used for writing data. + */ + private ByteArrayOutputStream currentBaos; + + private boolean closed = false; + + /** + * Initializes a new instance of the BigByteArrayOutputStream class with default buffer size. + */ public BigByteArrayOutputStream() { - baos.add(new ByteArrayOutputStream()); + baosList = new ArrayList<>(); + baosList.add(new ByteArrayOutputStream()); + try { + reset(); + } catch (IOException ignored) {} + } + + /** + * Initializes a new instance of the BigByteArrayOutputStream class with buffer size. + * + * @param bufferSize initial guaranteed buffer size + */ + public BigByteArrayOutputStream(int bufferSize) { + if (bufferSize < 0) + throw new IllegalArgumentException("Negative initial size: " + bufferSize); + baosList = new ArrayList<>(1); + baosList.add(new ByteArrayOutputStream(bufferSize)); + try { + reset(); + } catch (IOException ignored) {} + } + + /** + * Initializes a new instance of the BigByteArrayOutputStream class with buffer size. + * + * @param bufferSize initial guaranteed buffer size + */ + public BigByteArrayOutputStream(long bufferSize) { + if (bufferSize < 0) + throw new IllegalArgumentException("Negative initial size: " + bufferSize); + if (bufferSize <= ARRAY_SIZE_LIMIT) { + baosList = new ArrayList<>(1); + baosList.add(new ByteArrayOutputStream((int) bufferSize)); + } else { + final var requiredBaoss = (int) ((bufferSize - 1) / ARRAY_SIZE_LIMIT) + 1; // -1 to prevent creating a fully sized, but empty baos at the end if the buffer size is a multiple of ARRAY_SIZE_LIMIT + baosList = new ArrayList<>(requiredBaoss); + IntStream.range(0, requiredBaoss).forEachOrdered(i -> baosList.add(new ByteArrayOutputStream(ARRAY_SIZE_LIMIT))); + } + try { + reset(); + } catch (IOException ignored) {} } public List getBaos() { - return baos; + return baosList; } public void write(BigByteArrayOutputStream bbaos) throws IOException { - for (byte[] bao : bbaos.toByteArray()) { - for (Byte b : bao) { - write(b); - } - } - + write(bbaos.toByteArray()); } public long size() { - long ret = 0; - for (ByteArrayOutputStream ba : baos) { - ret += ba.size(); - } - return ret; + return baosList.stream().mapToLong(ByteArrayOutputStream::size).sum(); } - public synchronized byte[][] toByteArray() { - byte[][] ret = new byte[baos.size()][]; - for (int i = 0; i < baos.size(); i++) { - ret[i] = baos.get(i).toByteArray(); + public byte[][] toByteArray() { + byte[][] ret = new byte[baosList.size()][]; + for (int i = 0; i < baosList.size(); i++) { + ret[i] = baosList.get(i).toByteArray(); } return ret; } - - public void write(byte[] i) throws IOException { - for (byte b : i) { - write(b); + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) throw new IOException("Tried to write to a closed stream"); + + Objects.checkFromIndexSize(off, len, b.length); + final var space = ensureSpace(); + final var writeLength = Math.min(len, space); + this.currentBaos.write(b, off, writeLength); + final var remainingBytes = len - writeLength; + if (remainingBytes > 0) { + ensureSpace(); + this.currentBaos.write(b, off + writeLength, remainingBytes); } } - public void write(byte[][] i) throws IOException { - for (byte[] arr : i) { - for (byte b : arr) { - write(b); - } + public void write(byte[][] byteArray) throws IOException { + for (byte[] arr : byteArray) { + write(arr); } } - public void write(byte i) throws IOException { - ByteArrayOutputStream current = baos.get(baos.size() - 1); - current = ensureSpace(current); - current.write(i); + public void write(byte b) throws IOException { + if (closed) throw new IOException("Tried to write to a closed stream"); + + ensureSpace(); + this.currentBaos.write(b); } @Override public void write(int i) throws IOException { - ByteArrayOutputStream current = baos.get(baos.size() - 1); - current = ensureSpace(current); - current.write(i); + if (closed) throw new IOException("Tried to write to a closed stream"); + + ensureSpace(); + this.currentBaos.write(i); } - private ByteArrayOutputStream ensureSpace(ByteArrayOutputStream current) { - if (current.size() == 2147483639) { - baos.add(new ByteArrayOutputStream()); + + private int ensureSpace() { + var space = ARRAY_SIZE_LIMIT - currentBaos.size(); + if (space == 0) { + space = ARRAY_SIZE_LIMIT; + if (baosListIndex == baosList.size() - 1) { + baosListIndex++; + currentBaos = new ByteArrayOutputStream(ARRAY_SIZE_LIMIT); + baosList.add(currentBaos); + } else { + baosListIndex++; + currentBaos = baosList.get(baosListIndex); + currentBaos.reset(); + } } - return baos.get(baos.size() - 1); + return space; } - public String toString(String charset) throws UnsupportedEncodingException { - StringBuilder builder = new StringBuilder(); - for(ByteArrayOutputStream baos : this.baos){ - builder.append(baos.toString(charset)); + /** + * Resets the state of the object by setting the baosListIndex to zero + * and assigning the first ByteArrayOutputStream in the baosList to the + * currentBaos variable. No {@link ByteArrayOutputStream}s are actually removed. + */ + public void reset() throws IOException { + if (closed) throw new IOException("Tried to reset to a closed stream"); + + currentBaos = baosList.get(baosListIndex = 0); + for (var baos : baosList) { + baos.reset(); } - return builder.toString(); } - public String toString(Charset charset) throws UnsupportedEncodingException { - return toString(charset.toString()); + /** + * Clears the state of the object by removing all {@link ByteArrayOutputStream}s + * from the baosList except for the first one. The baosListIndex is set to 1 + * and the currentBaos variable is reassigned to the first ByteArrayOutputStream + * in the baosList. + */ + public void clear() throws IOException { + if (closed) throw new IOException("Tried to clear to a closed stream"); + + if (baosList.size() > 1) + baosList.subList(1, this.baosList.size()).clear(); + currentBaos = baosList.get(baosListIndex = 0); + currentBaos.reset(); } - public Long countMatches(char s) { - //read - long count=0; - for(ByteArrayOutputStream baos : this.baos){ - for(byte b : baos.toByteArray()){ - if(b==s){ - count++; - } - } - } - return count; + @Override + public void close() throws IOException { + this.closed = true; } } \ No newline at end of file diff --git a/src/main/java/org/aksw/iguana/commons/numbers/NumberUtils.java b/src/main/java/org/aksw/iguana/commons/numbers/NumberUtils.java deleted file mode 100644 index 2c8629039..000000000 --- a/src/main/java/org/aksw/iguana/commons/numbers/NumberUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.aksw.iguana.commons.numbers; - -/** - * Utils class for everything with numbers - * - * @author f.conrads - * - */ -public class NumberUtils { - - /** - * Returns either a long represantation of the String nm or null. - * - * @param nm String which should be parsed - * @return String as a long representation if String is a Long, otherwise null - */ - public static Long getLong(String nm) { - try { - Long ret = Long.parseLong(nm); - return ret; - }catch(NumberFormatException e) {} - return null; - } - - /** - * Returns either a double representation of the String nm or null. - * - * @param nm String which should be parsed - * @return String as a double representation if String is a double, otherwise null - */ - public static Double getDouble(String nm) { - try { - return Double.parseDouble(nm); - } catch (NumberFormatException | NullPointerException ignored) { - } - return null; - } - -} diff --git a/src/main/java/org/aksw/iguana/commons/rdf/IONT.java b/src/main/java/org/aksw/iguana/commons/rdf/IONT.java index b9027f9c0..bc06b1548 100644 --- a/src/main/java/org/aksw/iguana/commons/rdf/IONT.java +++ b/src/main/java/org/aksw/iguana/commons/rdf/IONT.java @@ -1,6 +1,6 @@ package org.aksw.iguana.commons.rdf; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; +import org.aksw.iguana.cc.metrics.Metric; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; @@ -9,7 +9,6 @@ public class IONT { public static final String PREFIX = "iont"; public static final Resource suite = ResourceFactory.createResource(NS + "Suite"); - public static final Resource experiment = ResourceFactory.createResource(NS + "Experiment"); public static final Resource dataset = ResourceFactory.createResource(NS + "Dataset"); public static final Resource task = ResourceFactory.createResource(NS + "Task"); public static final Resource connection = ResourceFactory.createResource(NS + "Connection"); @@ -20,11 +19,7 @@ public class IONT { public static final Resource metric = ResourceFactory.createResource(NS + "Metric"); public static Resource getMetricClass(Metric metric) { - // TODO: compare with stresstest class + // TODO: compare with stresstest class (stresstest class as a subclass of Task is iont:Stresstest while QPS for example is iont:metric/QPS) return ResourceFactory.createResource(NS + "metric/" + metric.getAbbreviation()); } - - public static Resource getClass(String classname) { - return ResourceFactory.createResource(NS + classname); - } } diff --git a/src/main/java/org/aksw/iguana/commons/rdf/IPROP.java b/src/main/java/org/aksw/iguana/commons/rdf/IPROP.java index 81eeddd20..dcda72e89 100644 --- a/src/main/java/org/aksw/iguana/commons/rdf/IPROP.java +++ b/src/main/java/org/aksw/iguana/commons/rdf/IPROP.java @@ -1,6 +1,6 @@ package org.aksw.iguana.commons.rdf; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; +import org.aksw.iguana.cc.metrics.Metric; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.ResourceFactory; @@ -8,9 +8,6 @@ public class IPROP { public static final String NS = IGUANA_BASE.NS + "properties" + "/"; public static final String PREFIX = "iprop"; - private IPROP() { - } - /** * The RDF-friendly version of the IPROP namespace * with trailing / character. @@ -23,44 +20,13 @@ public static Property createMetricProperty(Metric metric) { return ResourceFactory.createProperty(NS + metric.getAbbreviation()); } - /* - * SPARQL query properties - */ - public static final Property aggregations; - public static final Property filter; - public static final Property groupBy; - public static final Property having; - public static final Property offset; - public static final Property optional; - public static final Property orderBy; - public static final Property triples; - public static final Property union; - - /* - * Query Stats - */ - public static final Property failed; - public static final Property penalizedQPS; - public static final Property QPS; - public static final Property queryExecution; - public static final Property timeOuts; - - public static final Property totalTime; - public static final Property unknownException; - public static final Property wrongCodes; public static final Property succeeded = ResourceFactory.createProperty(NS, "succeeded"); - /* - * Each Query Stats - */ - public static final Property code; - public static final Property queryID; - public static final Property resultSize; - public static final Property run; - public static final Property success; - public static final Property time; + public static final Property responseBodyHash = ResourceFactory.createProperty(NS, "responseBodyHash"); + public static final Property responseBody = ResourceFactory.createProperty(NS, "responseBody"); + public static final Property startTime = ResourceFactory.createProperty(NS, "startTime"); + public static final Property httpCode = ResourceFactory.createProperty(NS, "httpCode"); - public static final Property experiment = ResourceFactory.createProperty(NS, "experiment"); public static final Property dataset = ResourceFactory.createProperty(NS, "dataset"); public static final Property task = ResourceFactory.createProperty(NS, "task"); public static final Property connection = ResourceFactory.createProperty(NS, "connection"); @@ -78,34 +44,38 @@ public static Property createMetricProperty(Metric metric) { public static final Property startDate = ResourceFactory.createProperty(NS, "startDate"); public static final Property endDate = ResourceFactory.createProperty(NS, "endDate"); - static { + // Language Processor + public static final Property results = ResourceFactory.createProperty(NS, "results"); + public static final Property bindings = ResourceFactory.createProperty(NS, "bindings"); + public static final Property variable = ResourceFactory.createProperty(NS, "variable"); + public static final Property exception = ResourceFactory.createProperty(NS, "exception"); - // SPARQL query properties - aggregations = ResourceFactory.createProperty(NS, "aggregations"); - filter = ResourceFactory.createProperty(NS, "filter"); - groupBy = ResourceFactory.createProperty(NS, "groupBy"); - having = ResourceFactory.createProperty(NS, "having"); - offset = ResourceFactory.createProperty(NS, "offset"); - optional = ResourceFactory.createProperty(NS, "optional"); - orderBy = ResourceFactory.createProperty(NS, "orderBy"); - triples = ResourceFactory.createProperty(NS, "triples"); - union = ResourceFactory.createProperty(NS, "union"); - // Query Stats - failed = ResourceFactory.createProperty(NS, "failed"); - penalizedQPS = ResourceFactory.createProperty(NS, "penalizedQPS"); - QPS = ResourceFactory.createProperty(NS, "QPS"); - queryExecution = ResourceFactory.createProperty(NS, "queryExecution"); - timeOuts = ResourceFactory.createProperty(NS, "timeOuts"); + // SPARQL query properties + public static final Property aggregations = ResourceFactory.createProperty(NS, "aggregations"); + public static final Property filter = ResourceFactory.createProperty(NS, "filter"); + public static final Property groupBy = ResourceFactory.createProperty(NS, "groupBy"); + public static final Property having = ResourceFactory.createProperty(NS, "having"); + public static final Property offset = ResourceFactory.createProperty(NS, "offset"); + public static final Property optional = ResourceFactory.createProperty(NS, "optional"); + public static final Property orderBy = ResourceFactory.createProperty(NS, "orderBy"); + public static final Property triples = ResourceFactory.createProperty(NS, "triples"); + public static final Property union = ResourceFactory.createProperty(NS, "union"); - totalTime = ResourceFactory.createProperty(NS, "totalTime"); - unknownException = ResourceFactory.createProperty(NS, "unknownException"); - wrongCodes = ResourceFactory.createProperty(NS, "wrongCodes"); - // Each Query Stats - code = ResourceFactory.createProperty(NS, "code"); - queryID = ResourceFactory.createProperty(NS, "queryID"); - resultSize = ResourceFactory.createProperty(NS, "resultSize"); - run = ResourceFactory.createProperty(NS, "run"); - success = ResourceFactory.createProperty(NS, "success"); - time = ResourceFactory.createProperty(NS, "time"); - } + // Query Stats + public static final Property failed = ResourceFactory.createProperty(NS, "failed"); + public static final Property penalizedQPS = ResourceFactory.createProperty(NS, "penalizedQPS"); + public static final Property QPS = ResourceFactory.createProperty(NS, "QPS"); + public static final Property queryExecution = ResourceFactory.createProperty(NS, "queryExecution"); + public static final Property timeOuts = ResourceFactory.createProperty(NS, "timeOuts"); + public static final Property totalTime = ResourceFactory.createProperty(NS, "totalTime"); + public static final Property unknownException = ResourceFactory.createProperty(NS, "unknownException"); + public static final Property wrongCodes = ResourceFactory.createProperty(NS, "wrongCodes"); + + // Each Query Stats + public static final Property code = ResourceFactory.createProperty(NS, "code"); + public static final Property queryID = ResourceFactory.createProperty(NS, "queryID"); + public static final Property resultSize = ResourceFactory.createProperty(NS, "resultSize"); + public static final Property run = ResourceFactory.createProperty(NS, "run"); + public static final Property success = ResourceFactory.createProperty(NS, "success"); + public static final Property time = ResourceFactory.createProperty(NS, "time"); } diff --git a/src/main/java/org/aksw/iguana/commons/rdf/IRES.java b/src/main/java/org/aksw/iguana/commons/rdf/IRES.java index 49a01ea3d..c24768f68 100644 --- a/src/main/java/org/aksw/iguana/commons/rdf/IRES.java +++ b/src/main/java/org/aksw/iguana/commons/rdf/IRES.java @@ -1,47 +1,63 @@ package org.aksw.iguana.commons.rdf; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.worker.HttpWorker; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import java.math.BigInteger; +/** + * Class containing the IRES vocabulary and methods to create RDF resources. + */ public class IRES { public static final String NS = IGUANA_BASE.NS + "resource" + "/"; public static final String PREFIX = "ires"; - private IRES() { - } - - /** - * The RDF-friendly version of the IRES namespace - * with trailing / character. - */ - public static String getURI() { - return NS; - } - public static Resource getResource(String id) { return ResourceFactory.createResource(NS + id); } - public static Resource getWorkerResource(String taskID, int workerID) { - return ResourceFactory.createResource(NS + taskID + "/" + workerID); + public static Resource getMetricResource(Metric metric) { + return ResourceFactory.createResource(NS + metric.getAbbreviation()); } - public static Resource getTaskQueryResource(String taskID, String queryID) { - return ResourceFactory.createResource(NS + taskID + "/" + queryID); + public static Resource getResponsebodyResource(long hash) { + return ResourceFactory.createResource(NS + "responseBody" + "/" + hash); } - public static Resource getWorkerQueryResource(String taskID, int workerID, String queryID) { - return ResourceFactory.createResource(NS + taskID + "/" + workerID + "/" + queryID); - } + public static class Factory { - public static Resource getMetricResource(Metric metric) { - return ResourceFactory.createResource(NS + metric.getAbbreviation()); - } + private final String suiteID; + private final String taskURI; + + public Factory(String suiteID, long taskID) { + this.suiteID = suiteID; + this.taskURI = NS + suiteID + "/" + taskID; + } + + public Resource getSuiteResource() { + return ResourceFactory.createResource(NS + suiteID); + } + + public Resource getTaskResource() { + return ResourceFactory.createResource(this.taskURI); + } + + public Resource getWorkerResource(HttpWorker worker) { + return ResourceFactory.createResource(this.taskURI + "/" + worker.getWorkerID()); + } + + public Resource getTaskQueryResource(String queryID) { + return ResourceFactory.createResource(this.taskURI + "/" + queryID); + } + + public Resource getWorkerQueryResource(HttpWorker worker, int index) { + return ResourceFactory.createResource(this.taskURI + "/" + worker.getWorkerID() + "/" + worker.config().queries().getQueryId(index)); + } - public static Resource getWorkerQueryRunResource(String taskID, int workerID, String queryID, BigInteger run) { - return ResourceFactory.createResource(NS + taskID + "/" + workerID + "/" + queryID + "/" + run); + public Resource getWorkerQueryRunResource(HttpWorker worker, int index, BigInteger run) { + return ResourceFactory.createResource(this.taskURI + "/" + worker.getWorkerID() + "/" + worker.config().queries().getQueryId(index) + "/" + run); + } } } diff --git a/src/main/java/org/aksw/iguana/commons/reflect/ShorthandMapper.java b/src/main/java/org/aksw/iguana/commons/reflect/ShorthandMapper.java deleted file mode 100644 index 46d84fe5d..000000000 --- a/src/main/java/org/aksw/iguana/commons/reflect/ShorthandMapper.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.aksw.iguana.commons.reflect; - -import org.aksw.iguana.commons.annotation.Shorthand; -import org.reflections.Configuration; -import org.reflections.Reflections; -import org.reflections.scanners.*; -import org.reflections.util.ConfigurationBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * Maps the shorthand to the class names at the beginning of it's initialization. - * Thus it has to be done once. - * - */ -public class ShorthandMapper { - - public Logger LOGGER = LoggerFactory.getLogger(getClass()); - - private Map shortMap = new HashMap(); - - private static ShorthandMapper instance; - - public static ShorthandMapper getInstance(){ - if(instance==null){ - instance = new ShorthandMapper(); - } - return instance; - } - - - public ShorthandMapper(){ - this("org"); - } - - /** - * create mapping, but only searches in packages with the prefix - * @param prefix package prefix to check - */ - public ShorthandMapper(String prefix){ - try { - Configuration config = ConfigurationBuilder.build(prefix).addScanners(new TypeAnnotationsScanner()).addScanners(new SubTypesScanner()); - Reflections reflections = new Reflections(new String[]{"", prefix}); - - Set> annotatedClasses = reflections.getTypesAnnotatedWith(Shorthand.class); - LOGGER.info("Found {} annotated classes", annotatedClasses.size()); - LOGGER.info("Annotated Classes : {}", annotatedClasses.toString()); - ClassLoader cloader = ClassLoader.getSystemClassLoader(); - for (Class annotatedClass : annotatedClasses) { - Shorthand annotation = (Shorthand) annotatedClass.getAnnotation(Shorthand.class); - if (annotation == null) { - continue; - } - if (shortMap.containsKey(annotation.value())) { - LOGGER.warn("Shorthand Key {} for Class {} already exists, pointing to Class {}. ", annotation.value(), shortMap.get(annotation.value()), annotatedClass.getCanonicalName()); - } - shortMap.put(annotation.value(), annotatedClass.getCanonicalName()); - } - }catch(Exception e){ - LOGGER.error("Could not create shorthand mapping", e); - } - } - - public Map getShortMap() { - return shortMap; - } -} diff --git a/src/main/java/org/aksw/iguana/commons/script/ScriptExecutor.java b/src/main/java/org/aksw/iguana/commons/script/ScriptExecutor.java deleted file mode 100644 index 6f7aac7be..000000000 --- a/src/main/java/org/aksw/iguana/commons/script/ScriptExecutor.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * - */ -package org.aksw.iguana.commons.script; - -import org.apache.commons.exec.ExecuteException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; - -/** - * Class to execute Shell Scripts - * - * @author f.conrads - * - */ -public class ScriptExecutor { - - private static final Logger LOGGER = LoggerFactory.getLogger(ScriptExecutor.class); - - /** - * Will execute the given file with the provided arguments - * via Shell. - * - * @param file file to execute - * @param args arguments to execute file with - * @throws ExecuteException if script can't be executed - * @throws IOException if file IO errors - * @return Process return, 0 means everything worked fine - */ - public static int exec(String file, String[] args) throws ExecuteException, IOException{ - String fileName = new File(file).getAbsolutePath(); - - String[] shellCommand = new String[1 + (args == null ? 0 : args.length)]; - shellCommand[0] = fileName; - - if(args != null && args.length!=0) - { - System.arraycopy(args, 0, shellCommand, 1, args.length); - } - - return execute(shellCommand); - } - - /**Checks if file contains arguments itself - * - * @param file file to execute - * @param args arguments to execute file with - * @return Process return, 0 means everything worked fine - * @throws ExecuteException if script can't be executed - * @throws IOException if file IO errors - */ - public static int execSafe(String file, String[] args) throws ExecuteException, IOException{ - String actualScript = file; - String[] args2 = args; - if(file.contains(" ")){ - - String[] providedArguments = file.split("\\s+"); - args2 = new String[providedArguments.length-1+args.length]; - actualScript=providedArguments[0]; - int i=1; - for(i=1;i threadBuffer = ThreadLocal.withInitial(() -> new byte[bufferSize]); - - protected static final ThreadLocal threadByteArrayOutputStream = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(bufferSize)); - - /** - * Fastest way to serialize a stream to UTF-8 according to this stackoverflow question. - * - * @param inputStream the stream to read from - * @return the content of inputStream as a string. - * @throws IOException from {@link InputStream#read()} - */ - static public ByteArrayOutputStream inputStream2String(InputStream inputStream) throws IOException { - ByteArrayOutputStream result = threadByteArrayOutputStream.get(); - result.reset(); - try { - inputStream2ByteArrayOutputStream(inputStream, null, -1.0, result); - } catch (TimeoutException e) { - // never happens - System.exit(-1); - } - return result; - } - - /** - * Fastest way to serialize a stream to UTF-8 according to this stackoverflow question. - * - * @param inputStream the stream to read from - * @param startTime a time when the computation started - * @param timeout delta from startTime when the computation must be completed. Otherwise, a TimeoutException may be thrown. Timeout check is deactivated if timeout is < 0. - * @return the content of inputStream as a string. - * @throws IOException from {@link InputStream#read()} - * @throws TimeoutException Maybe thrown any time after if startTime + timeout is exceeded - */ - static public ByteArrayOutputStream inputStream2String(InputStream inputStream, Instant startTime, double timeout) throws IOException, TimeoutException { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - inputStream2ByteArrayOutputStream(inputStream, startTime, timeout, result); - return result; - } - - /** - * Fastest way to serialize a stream to UTF-8 according to this stackoverflow question. - * - * @param inputStream the stream to read from - * @param startTime a time when the computation started - * @param timeout delta from startTime when the computation must be completed. Otherwise, a TimeoutException may be thrown. Timeout check is deactivated if timeout is < 0. - * @param result the stream where the result is written to. - * @return size of the output stream - * @throws IOException from {@link InputStream#read()} - * @throws TimeoutException Maybe thrown any time after if startTime + timeout is exceeded - */ - public static long inputStream2ByteArrayOutputStream(InputStream inputStream, Instant startTime, double timeout, ByteArrayOutputStream result) throws IOException, TimeoutException { - assert (result != null); - boolean enable_timeout = timeout > 0; - byte[] buffer = threadBuffer.get(); - int length; - while ((length = inputStream.read(buffer)) != -1) { - if (enable_timeout && durationInMilliseconds(startTime, Instant.now()) > timeout) - throw new TimeoutException("reading the answer timed out"); - result.write(buffer, 0, length); - } - return result.size(); - } - - /** - * Fastest way to serialize a stream to UTF-8 according to this stackoverflow question. - * - * @param inputStream the stream to read from - * @param result the stream where the result is written to. - * @return size of the output stream - * @throws IOException from {@link InputStream#read()} - */ - public static long inputStream2ByteArrayOutputStream(InputStream inputStream, ByteArrayOutputStream result) throws IOException { - try { - return inputStream2ByteArrayOutputStream(inputStream, Instant.now(), -1, result); - } catch (TimeoutException e) { - //will never happen - return 0; - } - } - - /** - * reads a stream and throws away the result. - * - * @param inputStream the stream to read from - * @param timeout delta from startTime when the computation must be completed. Otherwise, a TimeoutException may be thrown. Timeout check is deactivated if timeout is < 0. - * @return size of the output stream - * @throws IOException from {@link InputStream#read()} - * @throws TimeoutException Maybe thrown any time after if startTime + timeout is exceeded - */ - static public long inputStream2Length(InputStream inputStream, Instant startTime, double timeout) throws IOException, TimeoutException { - byte[] buffer = threadBuffer.get(); - long length; - long ret = 0; - while ((length = inputStream.read(buffer)) != -1) { - if (durationInMilliseconds(startTime, Instant.now()) > timeout && timeout > 0) - throw new TimeoutException("reading the answer timed out"); - ret += length; - } - return ret; - } -} diff --git a/src/main/java/org/aksw/iguana/commons/time/TimeUtils.java b/src/main/java/org/aksw/iguana/commons/time/TimeUtils.java index 46c2e13fa..4a7777689 100644 --- a/src/main/java/org/aksw/iguana/commons/time/TimeUtils.java +++ b/src/main/java/org/aksw/iguana/commons/time/TimeUtils.java @@ -1,47 +1,37 @@ package org.aksw.iguana.commons.time; import org.apache.jena.datatypes.xsd.XSDDuration; +import org.apache.jena.datatypes.xsd.impl.XSDDateTimeStampType; +import org.apache.jena.datatypes.xsd.impl.XSDDateTimeType; import org.apache.jena.datatypes.xsd.impl.XSDDurationType; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.ResourceFactory; import java.math.BigDecimal; import java.math.BigInteger; import java.time.Duration; import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; /** - * Everythin related to time stuff + * Class related to the conversion of Java time objects to RDF literals. */ public class TimeUtils { - /** - * returns the current time in Nanoseconds as a long instead of a double - * @return current time in nanoseconds as a long - */ - public static long getTimeInNanoseconds() { - Instant now = Instant.now(); - return ((long)now.getNano() + now.getEpochSecond() * 1000000000 /*ns*/); + public static XSDDuration toXSDDurationInSeconds(Duration duration) { + return (XSDDuration) new XSDDurationType().parse("PT" + new BigDecimal(BigInteger.valueOf(duration.toNanos()), 9).toPlainString() + "S"); } - /** - * gets the current time in milliseconds - * @return the current time in ms - */ - public static double getTimeInMilliseconds() { - return getTimeInNanoseconds() / 1000000d /*ms*/; + public static Literal createTypedDurationLiteral(Duration duration) { + return ResourceFactory.createTypedLiteral(new XSDDurationType().parse(duration.toString())); } - /** - * returns the duration in MS between two Time Instants - * @param start Start time - * @param end end time - * @return duration in ms between start and end - */ - public static double durationInMilliseconds(Instant start, Instant end) { - Duration duration = Duration.between(start, end); - return ((long)duration.getNano() + duration.getSeconds() * 1000000000 /*ns*/) / 1000000d /*ms*/; + public static Literal createTypedInstantLiteral(Instant time) { + return ResourceFactory.createTypedLiteral(new XSDDateTimeStampType(null).parse(time.toString())); } - public static XSDDuration toXSDDurationInSeconds(Duration duration) { - return (XSDDuration) new XSDDurationType().parse("PT" + new BigDecimal(BigInteger.valueOf(duration.toNanos()), 9).toPlainString() + "S"); + public static Literal createTypedZonedDateTimeLiteral(ZonedDateTime time) { + return ResourceFactory.createTypedLiteral(new XSDDateTimeStampType(null).parse(time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))); } } diff --git a/src/test/java/org/aksw/iguana/cc/config/ConfigTest.java b/src/test/java/org/aksw/iguana/cc/config/ConfigTest.java deleted file mode 100644 index 68a922e11..000000000 --- a/src/test/java/org/aksw/iguana/cc/config/ConfigTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.aksw.iguana.cc.config; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -/** - * Checks if the config is read correctly as YAML as well as JSON and checks if the corresponding Task could be created - */ -@RunWith(Parameterized.class) -public class ConfigTest { - - private final Boolean valid; - private final String file; - public Logger LOGGER = LoggerFactory.getLogger(getClass()); - - @Parameterized.Parameters - public static Collection data(){ - Collection testData = new ArrayList(); - testData.add(new Object[]{"src/test/resources/iguana.yml", false}); - testData.add(new Object[]{"src/test/resources/iguana.json", false}); - testData.add(new Object[]{"src/test/resources/iguana-valid.yml", true}); - testData.add(new Object[]{"src/test/resources/iguana-valid.json", true}); - return testData; - } - - public ConfigTest(String file, Boolean valid){ - this.file=file; - this.valid=valid; - } - - @Test - public void checkValidity() throws IOException { - IguanaConfig config = IguanaConfigFactory.parse(new File(file)); - if(valid){ - assertNotNull(config); - } - else { - assertNull(config); - } - config = IguanaConfigFactory.parse(new File(file), false); - assertNotNull(config); - } - - - -} diff --git a/src/test/java/org/aksw/iguana/cc/config/WorkflowTest.java b/src/test/java/org/aksw/iguana/cc/config/WorkflowTest.java deleted file mode 100644 index e9e107919..000000000 --- a/src/test/java/org/aksw/iguana/cc/config/WorkflowTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.aksw.iguana.cc.config; - -import org.aksw.iguana.cc.tasks.MockupStorage; -import org.aksw.iguana.cc.tasks.stresstest.metrics.Metric; -import org.aksw.iguana.cc.tasks.stresstest.metrics.MetricManager; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.*; -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; -import org.aksw.iguana.cc.tasks.stresstest.storage.StorageManager; -import org.aksw.iguana.cc.tasks.stresstest.storage.impl.NTFileStorage; -import org.apache.commons.io.FileUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class WorkflowTest { - private String file = "src/test/resources/config/mockupworkflow.yml"; - private String noDefaultFile = "src/test/resources/config/mockupworkflow-no-default.yml"; - private String preFile = "pre-shouldNotExist.txt"; - private String postFile = "post-shouldNotExist.txt"; - - private String expectedPreContent="TestSystem DatasetName testfile.txt\nTestSystem2 DatasetName testfile.txt\nTestSystem DatasetName2 testfile2.txt\nTestSystem2 DatasetName2 testfile2.txt\n"; - private String expectedPostContent="testfile.txt DatasetName TestSystem\ntestfile.txt DatasetName TestSystem2\ntestfile2.txt DatasetName2 TestSystem\ntestfile2.txt DatasetName2 TestSystem2\n"; - - @After - @Before - public void cleanUp(){ - File pre = new File(preFile); - File post = new File(postFile); - pre.delete(); - post.delete(); - StorageManager storageManager = StorageManager.getInstance(); - storageManager.getStorages().clear(); - MetricManager.setMetrics(new ArrayList<>()); - } - - @Test - public void hooks() throws IOException { - IguanaConfig config = IguanaConfigFactory.parse(new File(noDefaultFile), false); - //test if workflow was correct - config.start(); - File pre = new File(preFile); - File post = new File(postFile); - - String preContent = FileUtils.readFileToString(pre, "UTF-8"); - String postContent = FileUtils.readFileToString(post, "UTF-8"); - assertEquals(expectedPreContent, preContent); - assertEquals(expectedPostContent, postContent); - - } - - @Test - public void workflowTest() throws IOException { - IguanaConfig config = IguanaConfigFactory.parse(new File(file), false); - //test if workflow was correct - config.start(); - StorageManager storageManager = StorageManager.getInstance(); - Set storages = storageManager.getStorages(); - assertEquals(1, storages.size()); - Storage s = storages.iterator().next(); - assertTrue(s instanceof MockupStorage); - } - - @Test - public void noDefaultTest() throws IOException { - IguanaConfig config = IguanaConfigFactory.parse(new File(noDefaultFile), false); - //test if correct defaults were loaded - config.start(); - StorageManager storageManager = StorageManager.getInstance(); - Set storages = storageManager.getStorages(); - assertEquals(1, storages.size()); - Storage s = storages.iterator().next(); - assertTrue(s instanceof MockupStorage); - - List metrics = MetricManager.getMetrics(); - assertEquals(2, metrics.size()); - Set> seen = new HashSet<>(); - for(Metric m : metrics){ - seen.add(m.getClass()); - } - assertEquals(2, seen.size()); - - assertTrue(seen.contains(QMPH.class)); - assertTrue(seen.contains(QPS.class)); - } - - @Test - public void initTest() throws IOException { - String file = "src/test/resources/config/mockupworkflow-default.yml"; - IguanaConfig config = IguanaConfigFactory.parse(new File(file), false); - //test if correct defaults were loaded - config.start(); - StorageManager storageManager = StorageManager.getInstance(); - Set storages = storageManager.getStorages(); - assertEquals(1, storages.size()); - Storage s = storages.iterator().next(); - assertTrue(s instanceof NTFileStorage); - File del = new File(((NTFileStorage)s).getFileName()); - del.delete(); - - List metrics = MetricManager.getMetrics(); - assertEquals(6, metrics.size()); - Set> seen = new HashSet<>(); - for(Metric m : metrics){ - seen.add(m.getClass()); - } - assertEquals(6, seen.size()); - - assertTrue(seen.contains(QMPH.class)); - assertTrue(seen.contains(QPS.class)); - assertTrue(seen.contains(AvgQPS.class)); - assertTrue(seen.contains(NoQPH.class)); - assertTrue(seen.contains(NoQ.class)); - assertTrue(seen.contains(AggregatedExecutionStatistics.class)); - } - -} diff --git a/src/test/java/org/aksw/iguana/cc/config/elements/ConnectionConfigTest.java b/src/test/java/org/aksw/iguana/cc/config/elements/ConnectionConfigTest.java new file mode 100644 index 000000000..32034d23e --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/config/elements/ConnectionConfigTest.java @@ -0,0 +1,185 @@ +package org.aksw.iguana.cc.config.elements; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.net.URI; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class ConnectionConfigTest { + private final ObjectMapper mapper = new ObjectMapper(); + + private static Stream testDeserializationData() { + return Stream.of( + Arguments.of(new ConnectionConfig( + "endpoint01", + "0.1", + null, + URI.create("http://example.com/sparql"), + null, null, null), + """ + { + "name":"endpoint01", + "endpoint":"http://example.com/sparql", + "version":"0.1", + "authentication": null, + "updateEndpoint":null, + "updateAuthentication":null, + "dataset":null + } + """ + ), + Arguments.of(new ConnectionConfig( + "endpoint01", + "0.1", + new DatasetConfig("MyData", "some.ttl"), + URI.create("http://example.com/sparql"), + null, null, null), + """ + {"name":"endpoint01","endpoint":"http://example.com/sparql","version":"0.1","authentication": null,"updateEndpoint":null,"dataset":{"name":"MyData","file":"some.ttl"}, "updateAuthentication": null} + """ + ), + Arguments.of(new ConnectionConfig( // test default values + "endpoint01", + null, + null, + URI.create("http://example.com/sparql"), + null, null, null), + """ + { + "name":"endpoint01", + "endpoint":"http://example.com/sparql" + } + """ + ), + Arguments.of(new ConnectionConfig( // test setting everything + "endpoint01", + "v2", + new DatasetConfig("dataset1", "some.ttl"), + URI.create("http://example.com/sparql"), + new ConnectionConfig.Authentication("user", "pass"), + URI.create("http://example.com/update"), + new ConnectionConfig.Authentication("user_update", "pass_update")), + """ + { + "name":"endpoint01", + "version": "v2", + "endpoint":"http://example.com/sparql", + "authentication": { + "user": "user", + "password": "pass" + }, + "updateEndpoint": "http://example.com/update", + "updateAuthentication": { + "user": "user_update", + "password": "pass_update" + }, + "dataset": { + "name": "dataset1", + "file": "some.ttl" + } + } + """ + ) + ); + } + + private static Stream testSerializationData() { + return Stream.of( + Arguments.of(new ConnectionConfig( + "endpoint01", + "0.1", + null, + URI.create("http://example.com/sparql"), + null, null, null), + """ + { + "name":"endpoint01", + "endpoint":"http://example.com/sparql", + "version":"0.1", + "authentication": null, + "updateEndpoint":null, + "updateAuthentication":null, + "dataset":null + } + """ + ), + Arguments.of(new ConnectionConfig( + "endpoint01", + "0.1", + new DatasetConfig("MyData", "some.ttl"), + URI.create("http://example.com/sparql"), + null, null, null), + """ + {"name":"endpoint01","endpoint":"http://example.com/sparql","version":"0.1","authentication": null,"updateEndpoint":null,"dataset":{"name":"MyData","file":"some.ttl"}, "updateAuthentication": null} + """ + ), + Arguments.of(new ConnectionConfig( // test default values + "endpoint01", + null, + null, + URI.create("http://example.com/sparql"), + null, null, null), + """ + { + "name":"endpoint01", + "endpoint":"http://example.com/sparql", + "version": null, + "dataset": null, + "authentication": null, + "updateAuthentication": null, + "updateEndpoint": null + } + """ + ), + Arguments.of(new ConnectionConfig( // test setting everything + "endpoint01", + "v2", + new DatasetConfig("dataset1", "some.ttl"), + URI.create("http://example.com/sparql"), + new ConnectionConfig.Authentication("user", "pass"), + URI.create("http://example.com/update"), + new ConnectionConfig.Authentication("user_update", "pass_update")), + """ + { + "name":"endpoint01", + "version": "v2", + "endpoint":"http://example.com/sparql", + "authentication": { + "user": "user", + "password": "pass" + }, + "updateEndpoint": "http://example.com/update", + "updateAuthentication": { + "user": "user_update", + "password": "pass_update" + }, + "dataset": { + "name": "dataset1", + "file": "some.ttl" + } + } + """ + ) + ); + } + + @ParameterizedTest + @MethodSource("testSerializationData") + public void testSerialization(ConnectionConfig config, String expectedJson) throws Exception { + final String actual = mapper.writeValueAsString(config); + assertEquals(mapper.readTree(expectedJson), mapper.readTree(actual)); + } + + @ParameterizedTest + @MethodSource("testDeserializationData") + public void testDeserialization(ConnectionConfig expected, String json) throws Exception { + final var actual = mapper.readValue(json, ConnectionConfig.class); + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/config/elements/DatasetConfigTest.java b/src/test/java/org/aksw/iguana/cc/config/elements/DatasetConfigTest.java new file mode 100644 index 000000000..9d09b7cb6 --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/config/elements/DatasetConfigTest.java @@ -0,0 +1,39 @@ +package org.aksw.iguana.cc.config.elements; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class DatasetConfigTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + private static Stream testData() { + return Stream.of( + Arguments.of( + new DatasetConfig("MyData", "some.ttl"), + """ + {"name":"MyData","file":"some.ttl"} + """ + ), + Arguments.of( + new DatasetConfig("MyData", null), + """ + {"name":"MyData"} + """ + ) + ); + } + + @ParameterizedTest + @MethodSource("testData") + public void testDeserialization(DatasetConfig expectedConfig, String json) throws Exception { + final var actualConfig = mapper.readValue(json, DatasetConfig.class); + assertEquals(expectedConfig, actualConfig); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/config/elements/StorageConfigTest.java b/src/test/java/org/aksw/iguana/cc/config/elements/StorageConfigTest.java new file mode 100644 index 000000000..0c99a46dd --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/config/elements/StorageConfigTest.java @@ -0,0 +1,51 @@ +package org.aksw.iguana.cc.config.elements; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.aksw.iguana.cc.storage.impl.CSVStorage; +import org.aksw.iguana.cc.storage.impl.RDFFileStorage; +import org.aksw.iguana.cc.storage.impl.TriplestoreStorage; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class StorageConfigTest { + private final ObjectMapper mapper = new ObjectMapper(); + + private static Stream testData() { + return Stream.of( + Arguments.of(new RDFFileStorage.Config("some.ttl"), + """ + {"type":"rdf file","path":"some.ttl"} + """ + ), + Arguments.of(new CSVStorage.Config("csv_results/"), + """ + {"type":"csv file","directory":"csv_results/"} + """ + ), + Arguments.of(new TriplestoreStorage.Config("http://example.com/sparql", "user", "pass", "http://example.com/"), + """ + {"type":"triplestore","endpoint":"http://example.com/sparql", "user": "user", "password": "pass", "baseUri": "http://example.com/"} + """ + ) + ); + } + + @ParameterizedTest + @MethodSource("testData") + public void testSerialization(StorageConfig config, String expectedJson) throws Exception { + final String actual = mapper.writeValueAsString(config); + assertEquals(mapper.readTree(expectedJson), mapper.readTree(actual)); + } + + @ParameterizedTest + @MethodSource("testData") + public void testDeserialization(StorageConfig expectedConfig, String json) throws Exception { + final var actualConfig = mapper.readValue(json, StorageConfig.class); + assertEquals(expectedConfig, actualConfig); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/lang/MockCloseableHttpResponse.java b/src/test/java/org/aksw/iguana/cc/lang/MockCloseableHttpResponse.java deleted file mode 100644 index 5d7fc06e4..000000000 --- a/src/test/java/org/aksw/iguana/cc/lang/MockCloseableHttpResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.aksw.iguana.cc.lang; - -import org.apache.http.HttpStatus; -import org.apache.http.ProtocolVersion; -import org.apache.http.ReasonPhraseCatalog; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.BasicHttpEntity; -import org.apache.http.message.BasicHttpResponse; -import org.apache.http.message.BasicStatusLine; - -import java.io.*; -import java.net.URL; -import java.util.Locale; - -public class MockCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse { - - public MockCloseableHttpResponse(StatusLine statusline, ReasonPhraseCatalog catalog, Locale locale) { - super(statusline, catalog, locale); - } - - public MockCloseableHttpResponse(StatusLine statusline) { - super(statusline); - } - - public MockCloseableHttpResponse(ProtocolVersion ver, int code, String reason) { - super(ver, code, reason); - } - - @Override - public void close() throws IOException { - - } - - public static CloseableHttpResponse buildMockResponse(String data, String contentType) throws FileNotFoundException, UnsupportedEncodingException { - ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); - String reasonPhrase = "OK"; - StatusLine statusline = new BasicStatusLine(protocolVersion, HttpStatus.SC_OK, reasonPhrase); - MockCloseableHttpResponse mockResponse = new MockCloseableHttpResponse(statusline); - BasicHttpEntity entity = new BasicHttpEntity(); - entity.setContentType(contentType); - //entity.setContentType(contentType); - URL url = Thread.currentThread().getContextClassLoader().getResource("response.txt"); - InputStream instream = new ByteArrayInputStream(data.getBytes()); - entity.setContent(instream); - mockResponse.setEntity(entity); - return mockResponse; - } -} diff --git a/src/test/java/org/aksw/iguana/cc/lang/RDFLanguageProcessorTest.java b/src/test/java/org/aksw/iguana/cc/lang/RDFLanguageProcessorTest.java deleted file mode 100644 index 37bb4176d..000000000 --- a/src/test/java/org/aksw/iguana/cc/lang/RDFLanguageProcessorTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.aksw.iguana.cc.lang; - -import org.aksw.iguana.cc.lang.impl.RDFLanguageProcessor; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.riot.Lang; -import org.json.simple.parser.ParseException; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.StringWriter; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; - -@RunWith(Parameterized.class) -public class RDFLanguageProcessorTest { - - private static Logger LOGGER = LoggerFactory.getLogger(RDFLanguageProcessorTest.class); - private final Lang lang; - private final Model m; - - @Parameterized.Parameters - public static Collection data() throws IllegalAccessException { - Collection testData = new ArrayList(); - for(Field langField : Lang.class.getFields()) { - Lang susLang = (Lang)langField.get(Lang.class); - if(susLang.equals(Lang.RDFTHRIFT) || susLang.equals(Lang.TRIX) || susLang.equals(Lang.SHACLC) || susLang.equals(Lang.TSV) || susLang.equals(Lang.CSV) || susLang.equals(Lang.RDFNULL)) { - //cannot test them as model doesn't allow them to write - continue; - } - testData.add(new Object[]{susLang}); - } - return testData; - } - - public RDFLanguageProcessorTest(Lang lang){ - this.lang = lang; - this.m = ModelFactory.createDefaultModel(); - m.add(ResourceFactory.createResource("uri://test"), ResourceFactory.createProperty("uri://prop1"), "abc"); - m.add(ResourceFactory.createResource("uri://test"), ResourceFactory.createProperty("uri://prop2"), "abc2"); - LOGGER.info("Testing Lanuage {} Content-Type: {}", lang.getName(), lang.getContentType()); - } - - @Test - public void testCorrectModel() throws IOException, ParserConfigurationException, SAXException, ParseException { - StringWriter sw = new StringWriter(); - m.write(sw, lang.getName(), null); - CloseableHttpResponse response = MockCloseableHttpResponse.buildMockResponse(sw.toString(), lang.getContentType().getContentTypeStr()); - RDFLanguageProcessor processor = new RDFLanguageProcessor(); - assertEquals(2, processor.getResultSize(response).longValue()); - } - - -} diff --git a/src/test/java/org/aksw/iguana/cc/lang/SPARQLLanguageProcessorTest.java b/src/test/java/org/aksw/iguana/cc/lang/SPARQLLanguageProcessorTest.java deleted file mode 100644 index 43a5da094..000000000 --- a/src/test/java/org/aksw/iguana/cc/lang/SPARQLLanguageProcessorTest.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.aksw.iguana.cc.lang; - -import org.aksw.iguana.cc.lang.impl.SPARQLLanguageProcessor; -import org.apache.jena.ext.com.google.common.collect.Lists; -import org.apache.jena.query.Query; -import org.apache.jena.query.QueryFactory; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.json.simple.parser.ParseException; -import org.junit.Test; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.ByteArrayOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class SPARQLLanguageProcessorTest { - - private String jsonResult = "{\n" + - " \"head\": { \"vars\": [ \"book\" , \"title\" ]\n" + - " } ,\n" + - " \"results\": { \n" + - " \"bindings\": [\n" + - " {\n" + - " \"book\": { \"type\": \"uri\" , \"value\": \"http://example.org/book/book3\" } ,\n" + - " \"title\": { \"type\": \"literal\" , \"value\": \"Example Book 3\" }\n" + - " } ,\n" + - " {\n" + - " \"book\": { \"type\": \"uri\" , \"value\": \"http://example.org/book/book2\" } ,\n" + - " \"title\": { \"type\": \"literal\" , \"value\": \"Example Book 2\" }\n" + - " } ,\n" + - " {\n" + - " \"book\": { \"type\": \"uri\" , \"value\": \"http://example.org/book/book1\" } ,\n" + - " \"title\": { \"type\": \"literal\" , \"value\": \"Example Book 1\" }\n" + - " }\n" + - " ]\n" + - " }\n" + - "}"; - private String xmlResult = "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n" + - " \n" + - " \n" + - " test1\n" + - " ... \n" + - " \n" + - "\n" + - " \n" + - " test2\n" + - " ... \n" + - " \n" + - " \n" + - " \n" + - "\n" + - ""; - - - - - @Test - public void checkJSON() throws ParseException, IOException { - ByteArrayOutputStream bbaos = new ByteArrayOutputStream(); - bbaos.write(jsonResult.getBytes()); - assertEquals(3, SPARQLLanguageProcessor.getJsonResultSize(bbaos)); - //test if valid json response provide 0 bindings - try { - //check if invalid json throws exception - bbaos = new ByteArrayOutputStream(); - bbaos.write("{ \"a\": \"b\"}".getBytes()); - SPARQLLanguageProcessor.getJsonResultSize(bbaos); - assertTrue("Should have thrown an error", false); - }catch(Exception e){ - assertTrue(true); - } - try { - //check if invalid json throws exception - bbaos = new ByteArrayOutputStream(); - bbaos.write("{ \"a\": \"b\"".getBytes()); - SPARQLLanguageProcessor.getJsonResultSize(bbaos); - assertTrue("Should have thrown an error", false); - }catch(Exception e){ - assertTrue(true); - } - } - - @Test - public void checkXML() throws IOException, SAXException, ParserConfigurationException { - ByteArrayOutputStream bbaos = new ByteArrayOutputStream(); - bbaos.write(xmlResult.getBytes(StandardCharsets.UTF_8)); - assertEquals(2, SPARQLLanguageProcessor.getXmlResultSize(bbaos)); - //test if valid xml response provide 0 bindings - try { - //check if invalid xml throws exception - bbaos = new ByteArrayOutputStream(); - bbaos.write("b".getBytes()); - SPARQLLanguageProcessor.getJsonResultSize(bbaos); - assertTrue("Should have thrown an error", false); - }catch(Exception e){ - assertTrue(true); - } - try { - //check if invalid xml throws exception - bbaos = new ByteArrayOutputStream(); - bbaos.write("{ \"a\": \"b\"".getBytes()); - SPARQLLanguageProcessor.getJsonResultSize(bbaos); - assertTrue("Should have thrown an error", false); - }catch(Exception e){ - assertTrue(true); - } - } - - @Test - public void checkResultSize() throws IOException, ParserConfigurationException, SAXException, ParseException { - SPARQLLanguageProcessor languageProcessor = new SPARQLLanguageProcessor(); - assertEquals(3, languageProcessor.getResultSize(MockCloseableHttpResponse.buildMockResponse(jsonResult, SPARQLLanguageProcessor.QUERY_RESULT_TYPE_JSON)).longValue()); - assertEquals(2, languageProcessor.getResultSize(MockCloseableHttpResponse.buildMockResponse(xmlResult, SPARQLLanguageProcessor.QUERY_RESULT_TYPE_XML)).longValue()); - assertEquals(4, languageProcessor.getResultSize(MockCloseableHttpResponse.buildMockResponse("a\na\na\nb", "text/plain")).longValue()); - } - - - @Test - public void checkGeneratedStatsModel() throws IOException { - Query q = QueryFactory.create("SELECT * {?s ?p ?o. ?o ?q ?t. FILTER(?t = \"abc\")} GROUP BY ?s"); - QueryWrapper wrapped = new QueryWrapper(q, "abc0"); - SPARQLLanguageProcessor languageProcessor = new SPARQLLanguageProcessor(); - Model actual = languageProcessor.generateTripleStats(Lists.newArrayList(wrapped),"query","1/1/2"); - Model expected = ModelFactory.createDefaultModel(); - expected.read(new FileReader("src/test/resources/querystats.nt"), null, "N-TRIPLE"); - assertEquals(expected.size(), actual.size()); - expected.remove(actual); - actual.write(new FileWriter("test2.nt"), "N-TRIPLE"); - assertEquals(0, expected.size()); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/mockup/MockupConnection.java b/src/test/java/org/aksw/iguana/cc/mockup/MockupConnection.java new file mode 100644 index 000000000..3e6d7bb05 --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/mockup/MockupConnection.java @@ -0,0 +1,19 @@ +package org.aksw.iguana.cc.mockup; + +import org.aksw.iguana.cc.config.elements.ConnectionConfig; + +import java.net.URI; + +public class MockupConnection { + + /** + * Creates a connection config with the given parameters + * + * @param name The name of the connection + * @param endpoint The endpoint of the connection + * @param datasetName The name of the dataset + */ + public static ConnectionConfig createConnectionConfig(String name, String datasetName, String endpoint) { + return new ConnectionConfig(name, "", null, URI.create(endpoint), null, null, null); + } +} diff --git a/src/test/java/org/aksw/iguana/cc/mockup/MockupQueryHandler.java b/src/test/java/org/aksw/iguana/cc/mockup/MockupQueryHandler.java new file mode 100644 index 000000000..6988f0ab9 --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/mockup/MockupQueryHandler.java @@ -0,0 +1,41 @@ +package org.aksw.iguana.cc.mockup; + +import org.aksw.iguana.cc.query.handler.QueryHandler; +import org.aksw.iguana.cc.query.selector.QuerySelector; +import org.aksw.iguana.cc.query.selector.impl.LinearQuerySelector; + + +public class MockupQueryHandler extends QueryHandler { + private final int id; + private final int queryNumber; + + public MockupQueryHandler(int id, int queryNumber) { + super(); + this.queryNumber = queryNumber; + this.id = id; + } + + @Override + public String getQueryId(int i) { + return "MockQueryHandler" + this.id + ":" + i; + } + + @Override + public String[] getAllQueryIds() { + String[] out = new String[queryNumber]; + for (int i = 0; i < queryNumber; i++) { + out[i] = getQueryId(i); + } + return out; + } + + @Override + public int getQueryCount() { + return queryNumber; + } + + @Override + public QuerySelector getQuerySelectorInstance() { + return new LinearQuerySelector(queryNumber); + } +} diff --git a/src/test/java/org/aksw/iguana/cc/mockup/MockupStorage.java b/src/test/java/org/aksw/iguana/cc/mockup/MockupStorage.java new file mode 100644 index 000000000..ef15adf9e --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/mockup/MockupStorage.java @@ -0,0 +1,18 @@ +package org.aksw.iguana.cc.mockup; + +import org.aksw.iguana.cc.storage.Storage; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; + +public class MockupStorage implements Storage { + private Model resultModel = ModelFactory.createDefaultModel(); + + @Override + public void storeResult(Model data) { + resultModel = data; + } + + public Model getResultModel() { + return resultModel; + } +} diff --git a/src/test/java/org/aksw/iguana/cc/mockup/MockupWorker.java b/src/test/java/org/aksw/iguana/cc/mockup/MockupWorker.java new file mode 100644 index 000000000..9950c9f9d --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/mockup/MockupWorker.java @@ -0,0 +1,118 @@ +package org.aksw.iguana.cc.mockup; + +import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import org.aksw.iguana.cc.config.elements.DatasetConfig; +import org.aksw.iguana.cc.query.handler.QueryHandler; +import org.aksw.iguana.cc.worker.HttpWorker; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; + +public class MockupWorker extends HttpWorker { + public record Config( + CompletionTarget completionTarget, + String acceptHeader, + Integer number, + Boolean parseResults, + QueryHandler queries, + ConnectionConfig connection, + Duration timeout + ) implements HttpWorker.Config {} + + + /** + * All values except the workerID and queries may be null. I recommend to use the MockupQueryHandler. + * I would also recommend to set the connection, if you want to use the StresstestResultProcessor. + */ + public MockupWorker(long workerID, CompletionTarget target, String acceptHeader, Integer number, Boolean parseResults, QueryHandler queries, ConnectionConfig connection, Duration timeout) { + super(workerID, null, new Config( + target, + acceptHeader, + number, + parseResults, + queries, + connection, + timeout + ) + ); + } + + /** + * All other values will be set to null. This is the bare minimum to make it work with the StresstestResultProcessor. + */ + public MockupWorker(long workerID, QueryHandler queries, String connectionName, String connectionVersion, String datasetName, Duration timeout) { + super(workerID, null, new Config( + null, + null, + null, + null, + queries, + new ConnectionConfig(connectionName, connectionVersion, new DatasetConfig(datasetName, null), null, null, null, null), + timeout + )); + } + + @Override + public CompletableFuture start() { + return null; + } + + public static List createWorkerResults(QueryHandler queries, List workers) { + final var startTime = ZonedDateTime.of(2023, 10, 11, 14, 14, 10, 0, ZoneId.of("UTC")); + final var endTime = ZonedDateTime.of(2023, 10, 12, 15, 15, 15, 0, ZoneId.of("UTC")); + + final var queryNumber = queries.getQueryCount(); + + Instant time = Instant.parse("2023-10-21T20:48:06.399Z"); + + final var results = new ArrayList(); + for (var worker : workers) { + final var exectutionStats = new ArrayList(); + for (int queryID = 0; queryID < queryNumber; queryID++) { + // successful execution + final var sucHttpCode = Optional.of(200); + final var sucDuration = Duration.ofSeconds(2); + final var sucLength = OptionalLong.of(1000); + final var responseBodyHash = OptionalLong.of(123); + time = time.plusSeconds(1); + exectutionStats.add(new ExecutionStats(queryID, time, sucDuration, sucHttpCode, sucLength, responseBodyHash, Optional.empty())); + + // failed execution (http error) + var failHttpCode = Optional.of(404); + var failDuration = Duration.ofMillis(500); + var failLength = OptionalLong.empty(); + var failResponseBodyHash = OptionalLong.empty(); + var failException = new Exception("httperror"); + time = time.plusSeconds(1); + exectutionStats.add(new ExecutionStats(queryID, time, failDuration, failHttpCode, failLength, failResponseBodyHash, Optional.of(failException))); + + // failed execution + failHttpCode = Optional.of(200); + failDuration = Duration.ofSeconds(5); + failLength = OptionalLong.empty(); + failResponseBodyHash = OptionalLong.of(456); + failException = new Exception("io_exception"); + time = time.plusSeconds(1); + exectutionStats.add(new ExecutionStats(queryID, time, failDuration, failHttpCode, failLength, failResponseBodyHash, Optional.of(failException))); + } + results.add(new Result(worker.getWorkerID(), exectutionStats, startTime, endTime)); + } + return results; + } + + public static List createWorkers(int idOffset, int workerNumber, QueryHandler queries, String connectionName, String connectionVersion, String datasetName) { + final var workers = new ArrayList(); + for (int i = idOffset; i < workerNumber + idOffset; i++) { + workers.add(new MockupWorker(i, queries, connectionName, connectionVersion, datasetName, Duration.ofSeconds(2))); + } + return workers; + } + +} diff --git a/src/test/java/org/aksw/iguana/cc/model/QueryResultHashKeyTest.java b/src/test/java/org/aksw/iguana/cc/model/QueryResultHashKeyTest.java deleted file mode 100644 index e2c1cc538..000000000 --- a/src/test/java/org/aksw/iguana/cc/model/QueryResultHashKeyTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.aksw.iguana.cc.model; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.UUID; - -import static org.junit.Assert.*; - -@RunWith(Parameterized.class) -public class QueryResultHashKeyTest { - - - private final String queryID; - private final long uniqueKey; - - @Parameterized.Parameters - public static Collection data(){ - Collection testData = new ArrayList(); - testData.add(new Object[]{"sparql1", 1}); - testData.add(new Object[]{"sparql2", 122323l}); - testData.add(new Object[]{"update", 122323l}); - testData.add(new Object[]{UUID.randomUUID().toString(), 122323l}); - testData.add(new Object[]{"", 0}); - return testData; - } - - public QueryResultHashKeyTest(String queryID, long uniqueKey){ - this.queryID=queryID; - this.uniqueKey=uniqueKey; - } - - @Test - public void checkEquals(){ - QueryResultHashKey key = new QueryResultHashKey(queryID, uniqueKey); - assertTrue(key.equals(key)); - assertFalse(key.equals(null)); - assertFalse(key.equals(queryID)); - assertFalse(key.equals(uniqueKey)); - QueryResultHashKey that = new QueryResultHashKey(queryID, uniqueKey); - assertEquals(key, that); - that = new QueryResultHashKey(queryID+"abc", uniqueKey); - assertNotEquals(key, that); - that = new QueryResultHashKey(queryID, uniqueKey+1); - assertNotEquals(key, that); - } - - -} diff --git a/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerConfigTest.java b/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerConfigTest.java new file mode 100644 index 000000000..ace718d9f --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerConfigTest.java @@ -0,0 +1,141 @@ +package org.aksw.iguana.cc.query.handler; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class QueryHandlerConfigTest { + private final ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); + + + + private static Stream testDeserializationData() { + return Stream.of( + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.FOLDER, + "", + true, + QueryHandler.Config.Order.LINEAR, + 100L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries","format":"folder","caching":true,"order":"linear","seed": 100, "lang":"SPARQL"} + """ + ), + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.ONE_PER_LINE, + "", + true, + QueryHandler.Config.Order.LINEAR, + 0L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries"} + """ + ), + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.FOLDER, + "", + true, + QueryHandler.Config.Order.RANDOM, + 42L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries","format":"folder","caching":true,"order":"random","seed":42,"lang":"SPARQL"} + """ + ), + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.SEPARATOR, + "\n", + true, + QueryHandler.Config.Order.RANDOM, + 42L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries","format":"separator", "separator": "\\n", "caching":true,"order":"random","seed":42,"lang":"SPARQL"} + """ + ) + ); + } + + private static Stream testSerializationData() { + return Stream.of( + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.FOLDER, + "", + true, + QueryHandler.Config.Order.LINEAR, + 100L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries","separator": "", "format":"folder","caching":true,"order":"linear","seed": 100, "lang":"SPARQL"} + """ + ), + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.ONE_PER_LINE, + "", + true, + QueryHandler.Config.Order.LINEAR, + 0L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries", "format":"one-per-line","separator":"","caching":true,"order":"linear","seed":0,"lang":"SPARQL"} + """ + ), + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.FOLDER, + "", + true, + QueryHandler.Config.Order.RANDOM, + 42L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries","format":"folder","separator":"","caching":true,"order":"random","seed":42,"lang":"SPARQL"} + """ + ), + Arguments.of(new QueryHandler.Config("some.queries", + QueryHandler.Config.Format.SEPARATOR, + "\n", + true, + QueryHandler.Config.Order.RANDOM, + 42L, + QueryHandler.Config.Language.SPARQL + ), + """ + {"path":"some.queries","format":"separator", "separator": "\\n", "caching":true,"order":"random","seed":42,"lang":"SPARQL"} + """ + ) + ); + } + + @ParameterizedTest + @MethodSource("testSerializationData") + public void testSerialisation(QueryHandler.Config config, String expectedJson) throws Exception { + + final String actual = mapper.writeValueAsString(config); + System.out.println(actual); + assertEquals(mapper.readTree(expectedJson), mapper.readTree(actual)); + } + + @ParameterizedTest + @MethodSource("testDeserializationData") + public void testDeserialization(QueryHandler.Config expected, String json) throws Exception { + final var actual = mapper.readValue(json, QueryHandler.Config.class); + + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerTest.java b/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerTest.java index 04ec00596..4235c1df2 100644 --- a/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerTest.java +++ b/src/test/java/org/aksw/iguana/cc/query/handler/QueryHandlerTest.java @@ -1,152 +1,185 @@ package org.aksw.iguana.cc.query.handler; -import org.aksw.iguana.cc.utils.ServerMock; -import org.apache.commons.io.FileUtils; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.simpleframework.http.core.ContainerServer; -import org.simpleframework.transport.connect.SocketConnection; - -import java.io.File; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.aksw.iguana.cc.query.selector.impl.LinearQuerySelector; +import org.aksw.iguana.cc.query.selector.impl.RandomQuerySelector; +import org.aksw.iguana.cc.query.source.QuerySource; +import org.aksw.iguana.cc.query.source.impl.FileLineQuerySource; +import org.aksw.iguana.cc.query.source.impl.FileSeparatorQuerySource; +import org.aksw.iguana.cc.query.source.impl.FolderQuerySource; +import org.aksw.iguana.cc.query.source.impl.FolderQuerySourceTest; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; -@RunWith(Parameterized.class) public class QueryHandlerTest { - private static final int FAST_SERVER_PORT = 8024; - private static final String CACHE_FOLDER = UUID.randomUUID().toString(); - private static ContainerServer fastServer; - private static SocketConnection fastConnection; + static Path parentFolder; + static Path tempDir; + static Path tempFileSep; + static Path tempFileLine; + + static List queries; + static List folderQueries; + + public static List data() { + final var out = new ArrayList(); + final var caching = List.of(true, false); + + for (var cache : caching) { + out.add(Arguments.of(String.format(""" + {"path":"%s","format":"folder","order":"linear","lang":"SPARQL", "caching": %s} + """, tempDir.toString().replaceAll("\\\\", "\\\\\\\\"), cache), + FolderQuerySource.class)); + out.add(Arguments.of(String.format(""" + {"path":"%s","format":"one-per-line","order":"linear","lang":"SPARQL", "caching": %s} + """, tempFileLine.toString().replaceAll("\\\\", "\\\\\\\\"), cache), + FileLineQuerySource.class)); + out.add(Arguments.of(String.format(""" + {"path":"%s","format":"separator", "separator": "\\n###\\n", "order":"linear","lang":"SPARQL", "caching": %s} + """, tempFileSep.toString().replaceAll("\\\\", "\\\\\\\\"), cache), + FileSeparatorQuerySource.class)); + } + + return out; + } - private final QueryHandler queryHandler; - private final Map config; - private final String[] expected; + @BeforeAll + public static void createFolder() throws IOException { + parentFolder = Files.createTempDirectory("iguana-query-handler-test"); + tempDir = Files.createTempDirectory(parentFolder, "folder-query-source-test-dir"); + tempFileSep = Files.createTempFile(parentFolder, "Query", ".txt"); + tempFileLine = Files.createTempFile(parentFolder, "Query", ".txt"); + + queries = new LinkedList<>(); + folderQueries = new LinkedList<>(); + + for (int i = 0; i < 10; i++) { + final Path queryFile = Files.createTempFile(tempDir, "Query", ".txt"); + final String content = UUID.randomUUID().toString(); + Files.writeString(queryFile, content); + Files.writeString(tempFileSep, content + "\n###\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND); + Files.writeString(tempFileLine, content + "\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND); + queries.add(new FolderQuerySourceTest.Query(queryFile, content)); + folderQueries.add(new FolderQuerySourceTest.Query(queryFile, content)); + } + // Queries in the folder are expected in alphabetic order of the file names. + Collections.sort(folderQueries); + } + @AfterAll + public static void removeFolder() throws IOException { + org.apache.commons.io.FileUtils.deleteDirectory(parentFolder.toFile()); + } - public QueryHandlerTest(Map config, String[] expected) { - this.queryHandler = new QueryHandler(config, 0); // workerID 0 results in correct seed for RandomSelector - this.config = config; - this.expected = expected; + @ParameterizedTest + @MethodSource("data") + public void testDeserialization(String json, Class sourceType) throws Exception { + final var mapper = new ObjectMapper(); + QueryHandler queryHandler = assertDoesNotThrow(() -> mapper.readValue(json, QueryHandler.class)); + final var selector = queryHandler.getQuerySelectorInstance(); + assertTrue(selector instanceof LinearQuerySelector); + assertEquals(queries.size(), queryHandler.getQueryCount()); + assertNotEquals(0, queryHandler.hashCode()); + for (int i = 0; i < queryHandler.getQueryCount(); i++) { + final var wrapper = queryHandler.getNextQuery(selector); + assertEquals(i, selector.getCurrentIndex()); + if (FolderQuerySource.class.isAssignableFrom(sourceType)) + assertEquals(folderQueries.get(i).content(), wrapper.query()); + else + assertEquals(queries.get(i).content(), wrapper.query()); + assertEquals(i, wrapper.index()); + } } - @Parameterized.Parameters - public static Collection data() throws IOException { - String le = org.aksw.iguana.cc.utils.FileUtils.getLineEnding("src/test/resources/query/source/queries.txt"); - - String[] opl = new String[]{"QUERY 1 {still query 1}", "QUERY 2 {still query 2}", "QUERY 3 {still query 3}", "QUERY 1 {still query 1}"}; - String[] folder = new String[]{"QUERY 1 {" + le + "still query 1" + le + "}", "QUERY 2 {" + le + "still query 2" + le + "}", "QUERY 3 {" + le + "still query 3" + le + "}", "QUERY 1 {" + le + "still query 1" + le + "}"}; - String[] separator = new String[]{"QUERY 1 {" + le + "still query 1" + le + "}", "QUERY 2 {" + le + "still query 2" + le + "}", "QUERY 3 {" + le + "still query 3" + le + "}", "QUERY 1 {" + le + "still query 1" + le + "}"}; - - Collection testData = new ArrayList<>(); - - // Defaults: one-per-line, caching, linear - Map config0 = new HashMap<>(); - config0.put("location", "src/test/resources/query/source/queries.txt"); - testData.add(new Object[]{config0, opl}); - - // Defaults: caching, linear - Map config1 = new HashMap<>(); - config1.put("location", "src/test/resources/query/source/query-folder"); - config1.put("format", "folder"); - testData.add(new Object[]{config1, folder}); - - // Defaults: separator("###"), caching, linear - Map config2 = new HashMap<>(); - config2.put("location", "src/test/resources/query/source/separated-queries-default.txt"); - config2.put("format", "separator"); - testData.add(new Object[]{config2, separator}); - - Map config3 = new HashMap<>(); - config3.put("location", "src/test/resources/query/source/separated-queries-default.txt"); - Map format3 = new HashMap<>(); - format3.put("separator", "###"); - config3.put("format", format3); - config3.put("caching", false); - config3.put("order", "random"); - testData.add(new Object[]{config3, separator}); - - // Defaults: one-per-line, caching - Map config4 = new HashMap<>(); - config4.put("location", "src/test/resources/query/source/queries.txt"); - Map random4 = new HashMap<>(); - random4.put("seed", 0); - Map order4 = new HashMap<>(); - order4.put("random", random4); - config4.put("order", order4); - testData.add(new Object[]{config4, opl}); - - String[] expectedInstances = new String[]{"SELECT ?book {?book ?o}", "SELECT ?book {?book ?o}", "SELECT ?book {?book ?o}", "SELECT ?book {?book ?o}"}; - Map config5 = new HashMap<>(); - config5.put("location", "src/test/resources/query/pattern-query.txt"); - Map pattern5 = new HashMap<>(); - pattern5.put("endpoint", "http://localhost:8024"); - pattern5.put("outputFolder", CACHE_FOLDER); - config5.put("pattern", pattern5); - testData.add(new Object[]{config5, expectedInstances}); - - - return testData; + @ParameterizedTest + @MethodSource("data") + public void testQueryStreamWrapper(String json, Class sourceType) throws IOException { + final var mapper = new ObjectMapper(); + QueryHandler queryHandler = assertDoesNotThrow(() -> mapper.readValue(json, QueryHandler.class)); + final var selector = queryHandler.getQuerySelectorInstance(); + assertTrue(selector instanceof LinearQuerySelector); + assertEquals(queries.size(), queryHandler.getQueryCount()); + assertNotEquals(0, queryHandler.hashCode()); + for (int i = 0; i < queryHandler.getQueryCount(); i++) { + final var wrapper = queryHandler.getNextQueryStream(selector); + assertEquals(i, selector.getCurrentIndex()); + final var acutalQuery = new String(wrapper.queryInputStream().readAllBytes(), StandardCharsets.UTF_8); + if (FolderQuerySource.class.isAssignableFrom(sourceType)) + assertEquals(folderQueries.get(i).content(), acutalQuery); + else + assertEquals(queries.get(i).content(), acutalQuery); + assertEquals(i, wrapper.index()); + } } - @BeforeClass - public static void startServer() throws IOException { - ServerMock fastServerContainer = new ServerMock(); - fastServer = new ContainerServer(fastServerContainer); - fastConnection = new SocketConnection(fastServer); - SocketAddress address1 = new InetSocketAddress(FAST_SERVER_PORT); - fastConnection.connect(address1); + @ParameterizedTest + @MethodSource("data") + public void testQueryStringWrapper(String json, Class sourceType) throws IOException { + final var mapper = new ObjectMapper(); + QueryHandler queryHandler = assertDoesNotThrow(() -> mapper.readValue(json, QueryHandler.class)); + final var selector = queryHandler.getQuerySelectorInstance(); + assertTrue(selector instanceof LinearQuerySelector); + assertEquals(queries.size(), queryHandler.getQueryCount()); + assertNotEquals(0, queryHandler.hashCode()); + for (int i = 0; i < queryHandler.getQueryCount(); i++) { + final var wrapper = queryHandler.getNextQuery(selector); + assertEquals(i, selector.getCurrentIndex()); + if (FolderQuerySource.class.isAssignableFrom(sourceType)) + assertEquals(folderQueries.get(i).content(), wrapper.query()); + else + assertEquals(queries.get(i).content(), wrapper.query()); + assertEquals(i, wrapper.index()); + } } - @AfterClass - public static void stopServer() throws IOException { - fastConnection.close(); - fastServer.stop(); - FileUtils.deleteDirectory(new File(CACHE_FOLDER)); + @ParameterizedTest + @MethodSource("data") + public void testQueryIDs(String json, Class sourceType) { + final var mapper = new ObjectMapper(); + QueryHandler queryHandler = assertDoesNotThrow(() -> mapper.readValue(json, QueryHandler.class)); + final var selector = queryHandler.getQuerySelectorInstance(); + assertTrue(selector instanceof LinearQuerySelector); + assertEquals(queries.size(), queryHandler.getQueryCount()); + assertNotEquals(0, queryHandler.hashCode()); + final var allQueryIDs = queryHandler.getAllQueryIds(); + for (int i = 0; i < queryHandler.getQueryCount(); i++) { + assertEquals(queryHandler.hashCode() + ":" + i, allQueryIDs[i]); + assertEquals(allQueryIDs[i], queryHandler.getQueryId(i)); + } } @Test - public void getNextQueryTest() throws IOException { - // Assumes, that the order is correct has only stored values for random retrieval - Object order = config.getOrDefault("order", null); - if (order != null) { - Collection queries = new HashSet<>(); - for (int i = 0; i < 4; i++) { - StringBuilder query = new StringBuilder(); - StringBuilder queryID = new StringBuilder(); - this.queryHandler.getNextQuery(query, queryID); - queries.add(query.toString()); + public void testRandomQuerySelectorSeedConsistency() throws IOException { + String[] json = new String[2]; + json[0] = String.format(""" + {"path":"%s","format":"folder","order":"random", "seed": 100,"lang":"SPARQL"} + """, tempDir.toString().replaceAll("\\\\", "\\\\\\\\")); // windows + json[1] = String.format(""" + {"path":"%s","format":"one-per-line","order":"random", "seed": 100,"lang":"SPARQL"} + """, tempFileLine.toString().replaceAll("\\\\", "\\\\\\\\")); // this tests need to different configuration, because instances of the query handler are cached + + final var mapper = new ObjectMapper(); + List[] indices = new ArrayList[2]; + for (int i = 0; i < 2; i++) { + QueryHandler queryHandler = mapper.readValue(json[i], QueryHandler.class); + final var selector = queryHandler.getQuerySelectorInstance(); + assertTrue(selector instanceof RandomQuerySelector); + indices[i] = new ArrayList<>(); + for (int j = 0; j < 100000; j++) { + indices[i].add(selector.getNextIndex()); } - assertTrue(Arrays.asList(this.expected).containsAll(queries)); - return; } - - StringBuilder query = new StringBuilder(); - StringBuilder queryID = new StringBuilder(); - this.queryHandler.getNextQuery(query, queryID); - assertEquals(this.expected[0], query.toString()); - - query = new StringBuilder(); - queryID = new StringBuilder(); - this.queryHandler.getNextQuery(query, queryID); - assertEquals(this.expected[1], query.toString()); - - query = new StringBuilder(); - queryID = new StringBuilder(); - this.queryHandler.getNextQuery(query, queryID); - assertEquals(this.expected[2], query.toString()); - - query = new StringBuilder(); - queryID = new StringBuilder(); - this.queryHandler.getNextQuery(query, queryID); - assertEquals(this.expected[3], query.toString()); + assertEquals(indices[0], indices[1]); } } \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/query/list/QueryListTest.java b/src/test/java/org/aksw/iguana/cc/query/list/QueryListTest.java new file mode 100644 index 000000000..b1da8ffac --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/query/list/QueryListTest.java @@ -0,0 +1,142 @@ +package org.aksw.iguana.cc.query.list; + +import org.aksw.iguana.cc.query.list.impl.FileBasedQueryList; +import org.aksw.iguana.cc.query.list.impl.InMemQueryList; +import org.aksw.iguana.cc.query.source.QuerySource; +import org.aksw.iguana.cc.query.source.impl.FileLineQuerySource; +import org.aksw.iguana.cc.query.source.impl.FileSeparatorQuerySource; +import org.aksw.iguana.cc.query.source.impl.FolderQuerySource; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class QueryListTest { + private enum QuerySourceType { + FILE_LINE, + FILE_SEPARATOR, + FOLDER, + } + + static Path tempDir; + static List cachedArguments = null; + + private static QueryList createQueryList(Class queryListClass,QuerySource querySource) { + try { + return (QueryList) queryListClass.getConstructor(QuerySource.class).newInstance(querySource); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @BeforeAll + public static void createFolder() throws IOException { + tempDir = Files.createTempDirectory("iguana-folder-query-source-test-dir"); + } + + @AfterAll + public static void deleteFolder() throws IOException { + FileUtils.deleteDirectory(tempDir.toFile()); + } + + public static List data() throws IOException { + if (cachedArguments != null) + return cachedArguments; + + final var queryListClasses = List.of(InMemQueryList.class, FileBasedQueryList.class); + final var querySources = List.of(QuerySourceType.FILE_SEPARATOR, QuerySourceType.FILE_LINE, QuerySourceType.FOLDER); + final var sizes = List.of(1, 2, 10, 100, 1000); + + final var out = new ArrayList(); + for (var size : sizes) { + for (var querySourceType : querySources) { + for (var queryListClass : queryListClasses) { + final var queries = new ArrayList(); + for (int i = 0; i < size; i++) { + final String queryString = UUID.randomUUID().toString(); + queries.add(queryString); + } + QuerySource querySource = null; + switch (querySourceType) { + case FOLDER -> { + final var queryDir = Files.createTempDirectory(tempDir, "query-dir"); + for (int i = 0; i < size; i++) { + String filePrefix = String.format("Query-%09d.txt", i); // to ensure that the order from the queries List is the same as the order of the files in the folder + final Path queryFile = Files.createTempFile(queryDir, filePrefix, ".txt"); + Files.write(queryFile, queries.get(i).getBytes()); + } + querySource = new FolderQuerySource(queryDir); + } + case FILE_LINE -> { + final var queryFile = Files.createTempFile(tempDir, "Query", ".txt"); + Files.write(queryFile, String.join("\n", queries).getBytes()); + querySource = new FileLineQuerySource(queryFile); + } + case FILE_SEPARATOR -> { + final var queryFile = Files.createTempFile(tempDir, "Query", ".txt"); + Files.write(queryFile, String.join("\n###\n", queries).getBytes()); + querySource = new FileSeparatorQuerySource(queryFile, "\n###\n"); + } + } + String querySourceConfigString = String.format("[ type=%s, size=%d ]", querySourceType, size); + out.add(Arguments.of(Named.of(queryListClass.getSimpleName(), queryListClass), Named.of(querySourceConfigString, querySource), queries)); + } + } + } + cachedArguments = out; + return out; + } + + public void testIllegalArguments() { + assertThrows(NullPointerException.class, () -> new InMemQueryList(null)); + assertThrows(NullPointerException.class, () -> new FileBasedQueryList(null)); + } + + @ParameterizedTest(name = "[{index}] queryListClass={0}, querySourceConfig={1}") + @MethodSource("data") + public void testGetQuery(Class queryListClass, QuerySource querySource, List expectedQueries) throws IOException { + final var queryList = createQueryList(queryListClass, querySource); + for (int i = 0; i < expectedQueries.size(); i++) { + final var expectedQuery = expectedQueries.get(i); + assertEquals(expectedQuery, queryList.getQuery(i)); + } + } + + @ParameterizedTest(name = "[{index}] queryListClass={0}, querySourceConfig={1}") + @MethodSource("data") + public void testGetQueryStream(Class queryListClass, QuerySource querySource, List expectedQueries) throws IOException { + final var queryList = createQueryList(queryListClass, querySource); + for (int i = 0; i < expectedQueries.size(); i++) { + final var expectedQuery = expectedQueries.get(i); + final var queryString = new String(queryList.getQueryStream(i).readAllBytes(), "UTF-8"); + assertEquals(expectedQuery, queryString); + } + } + + @ParameterizedTest(name = "[{index}] queryListClass={0}, querySourceConfig={1}") + @MethodSource("data") + public void testSize(Class queryListClass, QuerySource querySource, List expectedQueries) { + final var queryList = createQueryList(queryListClass, querySource); + assertEquals(expectedQueries.size(), queryList.size()); + } + + @ParameterizedTest(name = "[{index}] queryListClass={0}, querySourceConfig={1}") + @MethodSource("data") + public void testHashcode(Class queryListClass, QuerySource querySource, List expectedQueries) { + final var queryList = createQueryList(queryListClass, querySource); + assertTrue(queryList.hashCode() != 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/query/pattern/PatternBasedQueryHandlerTest.java b/src/test/java/org/aksw/iguana/cc/query/pattern/PatternBasedQueryHandlerTest.java deleted file mode 100644 index 2c082531c..000000000 --- a/src/test/java/org/aksw/iguana/cc/query/pattern/PatternBasedQueryHandlerTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.aksw.iguana.cc.query.pattern; - -import org.aksw.iguana.cc.query.source.QuerySource; -import org.aksw.iguana.cc.query.source.impl.FileLineQuerySource; -import org.apache.commons.io.FileUtils; -import org.apache.jena.ext.com.google.common.collect.Lists; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.util.*; -import java.util.stream.Stream; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@RunWith(Parameterized.class) -public class PatternBasedQueryHandlerTest { - private final String dir = UUID.randomUUID().toString(); - private String[] queryStr; - private File queriesFile; - - public PatternBasedQueryHandlerTest(String[] queryStr) { - this.queryStr = queryStr; - } - - @Parameterized.Parameters - public static Collection data() { - Collection testData = new ArrayList<>(); - testData.add(new Object[]{new String[]{"SELECT * {?s ?p ?o}"}}); - testData.add(new Object[]{new String[]{"SELECT * {?s ?p ?o}"}}); - - return testData; - } - - @Before - public void createFolder() throws IOException { - //File f = new File(this.dir); - //f.mkdir(); - String queryFile = UUID.randomUUID().toString(); - File f = new File(queryFile); - f.createNewFile(); - try (PrintWriter pw = new PrintWriter(f)) { - for (String query : this.queryStr) { - pw.println(query); - } - } - //remove empty lines after printing them, so the expected asserts will correctly assume that the empty limes are ignored - List tmpList = Lists.newArrayList(this.queryStr); - Iterator it = tmpList.iterator(); - while (it.hasNext()) { - if (it.next().isEmpty()) { - it.remove(); - } - } - this.queryStr = tmpList.toArray(new String[]{}); - this.queriesFile = f; - f.deleteOnExit(); - } - - @After - public void removeFolder() throws IOException { - File f = new File(this.dir); - FileUtils.deleteDirectory(f); - } - - - @Test - public void testQueryCreation() throws IOException { - QuerySource originalSource = getQuerySource(); - PatternHandler ph = new PatternHandler(getConfig(), originalSource); - QuerySource qs = ph.generateQuerySource(); - - //check if folder exist this.dir/hashCode/ with |queries| files - int hashcode = originalSource.hashCode(); - File f = new File(this.dir + File.separator + hashcode); - File outDir = new File(this.dir); - assertTrue(outDir.exists()); - assertTrue(outDir.isDirectory()); - assertTrue(f.isFile()); - - assertEquals(1, outDir.listFiles().length); - - int expectedNoOfQueries = this.queryStr.length; - assertEquals(expectedNoOfQueries, qs.size()); - - try (Stream stream = Files.lines(f.toPath())) { - assertEquals(expectedNoOfQueries, stream.count()); - } - - for (int i = 0; i < expectedNoOfQueries; i++) { - assertEquals(this.queryStr[i], qs.getQuery(i)); - } - - FileUtils.deleteDirectory(outDir); - } - - @Test - public void testCaching() throws IOException { - QuerySource originalSource = getQuerySource(); - PatternHandler ph = new PatternHandler(getConfig(), originalSource); - ph.generateQuerySource(); - - int hashcode = originalSource.hashCode(); - File f = new File(this.dir + File.separator + hashcode); - assertTrue(f.exists()); - assertTrue(f.isFile()); - - int contentHash = org.aksw.iguana.cc.utils.FileUtils.getHashcodeFromFileContent(f.getAbsolutePath()); - Map attr = Files.readAttributes(f.toPath(), "basic:creationTime"); - - PatternHandler ph2 = new PatternHandler(getConfig(), originalSource); - ph2.generateQuerySource(); - - int contentHash2 = org.aksw.iguana.cc.utils.FileUtils.getHashcodeFromFileContent(f.getAbsolutePath()); - assertEquals(contentHash, contentHash2); - - Map attr2 = Files.readAttributes(f.toPath(), "basic:creationTime"); - assertEquals(attr.get("creationTime"), attr2.get("creationTime")); - - FileUtils.deleteDirectory(new File(this.dir)); - } - - private Map getConfig() { - Map config = new HashMap<>(); - config.put("endpoint", "http://test.com"); - config.put("outputFolder", this.dir); - config.put("limit", 5); - return config; - } - - private QuerySource getQuerySource() { - return new FileLineQuerySource(this.queriesFile.getAbsolutePath()); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/query/pattern/PatternHandlerTest.java b/src/test/java/org/aksw/iguana/cc/query/pattern/PatternHandlerTest.java deleted file mode 100644 index c618815d9..000000000 --- a/src/test/java/org/aksw/iguana/cc/query/pattern/PatternHandlerTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.aksw.iguana.cc.query.pattern; - -import org.aksw.iguana.cc.query.source.QuerySource; -import org.aksw.iguana.cc.query.source.impl.FileLineQuerySource; -import org.aksw.iguana.cc.utils.ServerMock; -import org.apache.jena.ext.com.google.common.collect.Lists; -import org.apache.jena.ext.com.google.common.collect.Sets; -import org.apache.jena.query.ParameterizedSparqlString; -import org.apache.jena.query.Query; -import org.apache.jena.query.QueryFactory; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.simpleframework.http.core.ContainerServer; -import org.simpleframework.transport.connect.SocketConnection; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.*; - -import static org.junit.Assert.assertEquals; - -@RunWith(Parameterized.class) -public class PatternHandlerTest { - - private static final int FAST_SERVER_PORT = 8024; - private static ContainerServer fastServer; - private static SocketConnection fastConnection; - private final String service; - private final String queryStr; - private final Query expectedConversionQuery; - private final String[] vars; - private final String expectedReplacedQuery; - private final List expectedInstances; - private final String dir = UUID.randomUUID().toString(); - - - public PatternHandlerTest(String queryStr, String expectedConversionStr, String expectedReplacedQuery, String[] vars, String[] expectedInstances) { - this.service = "http://localhost:8024"; - - this.queryStr = queryStr; - this.expectedConversionQuery = QueryFactory.create(expectedConversionStr); - this.vars = vars; - this.expectedReplacedQuery = expectedReplacedQuery; - this.expectedInstances = Lists.newArrayList(expectedInstances); - } - - @Parameterized.Parameters - public static Collection data() { - Collection testData = new ArrayList<>(); - testData.add(new Object[]{"SELECT * {?s ?p ?o}", "SELECT * {?s ?p ?o}", "SELECT * {?s ?p ?o}", new String[]{}, new String[]{"SELECT * {?s ?p ?o}"}}); - testData.add(new Object[]{"SELECT ?book {?book %%var0%% ?o}", "SELECT DISTINCT ?var0 {?book ?var0 ?o} LIMIT 2000", "SELECT ?book {?book ?var0 ?o}", new String[]{"var0"}, new String[]{"SELECT ?book {?book ?o}", "SELECT ?book {?book ?o}"}}); - testData.add(new Object[]{"SELECT ?book {?book %%var0%% %%var1%%}", "SELECT DISTINCT ?var1 ?var0 {?book ?var0 ?var1} LIMIT 2000", "SELECT ?book {?book ?var0 ?var1}", new String[]{"var0", "var1"}, new String[]{"SELECT ?book {?book \"Example Book 2\"}", "SELECT ?book {?book \"Example Book 1\"}"}}); - - return testData; - } - - @BeforeClass - public static void startServer() throws IOException { - ServerMock fastServerContainer = new ServerMock(); - fastServer = new ContainerServer(fastServerContainer); - fastConnection = new SocketConnection(fastServer); - SocketAddress address1 = new InetSocketAddress(FAST_SERVER_PORT); - fastConnection.connect(address1); - } - - @AfterClass - public static void stopServer() throws IOException { - fastConnection.close(); - fastServer.stop(); - } - - @Test - public void testReplacement() { - Set varNames = new HashSet<>(); - String replacedQuery = getHandler().replaceVars(this.queryStr, varNames); - assertEquals(this.expectedReplacedQuery, replacedQuery); - assertEquals(Sets.newHashSet(vars), varNames); - } - - - @Test - public void testPatternExchange() { - List instances = getHandler().generateQueries(this.queryStr); - assertEquals(this.expectedInstances, instances); - } - - @Test - public void testConversion() { - // convert query - // retrieve instances - PatternHandler qh = getHandler(); - - ParameterizedSparqlString pss = new ParameterizedSparqlString(); - pss.setCommandText(qh.replaceVars(this.queryStr, Sets.newHashSet())); - - Query q = qh.convertToSelect(pss, Sets.newHashSet(this.vars)); - assertEquals(this.expectedConversionQuery, q); - } - - private PatternHandler getHandler() { - Map config = new HashMap<>(); - config.put("endpoint", this.service); - config.put("outputFolder", this.dir); - - QuerySource qs = new FileLineQuerySource("src/test/resources/workers/single-query.txt"); - - return new PatternHandler(config, qs); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelectorTest.java b/src/test/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelectorTest.java index 8f6d18947..ca508685b 100644 --- a/src/test/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelectorTest.java +++ b/src/test/java/org/aksw/iguana/cc/query/selector/impl/LinearQuerySelectorTest.java @@ -1,23 +1,29 @@ package org.aksw.iguana.cc.query.selector.impl; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class LinearQuerySelectorTest { - private LinearQuerySelector linearQuerySelector; - - @Before - public void setUp() { - this.linearQuerySelector = new LinearQuerySelector(5); + @ParameterizedTest() + @ValueSource(ints = {1, 2, 3, 4}) + public void getNextIndexTest(int size) { + final var linearQuerySelector = new LinearQuerySelector(size); + assertEquals(-1, linearQuerySelector.getCurrentIndex()); + for (int i = 0; i < 10; i++) { + int currentIndex = linearQuerySelector.getNextIndex(); + assertEquals(i % size, currentIndex); + assertEquals(currentIndex, linearQuerySelector.getCurrentIndex()); + } } @Test - public void getNextIndexTest() { - for (int i = 0; i < 10; i++) { - assertEquals(i % 5, this.linearQuerySelector.getNextIndex()); - } + public void ThrowOnLinearQuerySelectorSizeZero() { + final var size = 0; + assertThrows(IllegalArgumentException.class, () -> new LinearQuerySelector(size)); } } \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelectorTest.java b/src/test/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelectorTest.java new file mode 100644 index 000000000..2353d983c --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/query/selector/impl/RandomQuerySelectorTest.java @@ -0,0 +1,39 @@ +package org.aksw.iguana.cc.query.selector.impl; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +public class RandomQuerySelectorTest { + + @Test + public void testGetIndex() { + final var selector = new RandomQuerySelector(100, 10); + for (int i = 0; i < 10000; i++) { + int currentIndex = selector.getNextIndex(); + assertTrue(0 <= currentIndex && currentIndex < 100); + assertEquals(currentIndex, selector.getCurrentIndex()); + } + } + + @ParameterizedTest + @ValueSource(ints = {-1, 0}) + public void testThrowingOnIllegalSize(int size) { + assertThrows(IllegalArgumentException.class, () -> new RandomQuerySelector(-1, 0)); + assertThrows(IllegalArgumentException.class, () -> new RandomQuerySelector(0, 0)); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 100000}) + public void testSeedConsistency(int size) { + final var selector = new RandomQuerySelector(size, 0); + final var selector2 = new RandomQuerySelector(size, 0); + for (int i = 0; i < 100000; i++) { + final var nextIndex = selector.getNextIndex(); + final var nextIndex2 = selector2.getNextIndex(); + assert nextIndex == nextIndex2; + } + } +} diff --git a/src/test/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySourceTest.java b/src/test/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySourceTest.java index 7801fec80..7e3ce487c 100644 --- a/src/test/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySourceTest.java +++ b/src/test/java/org/aksw/iguana/cc/query/source/impl/FileLineQuerySourceTest.java @@ -1,49 +1,114 @@ package org.aksw.iguana.cc.query.source.impl; -import org.aksw.iguana.cc.utils.FileUtils; -import org.junit.Test; +import org.apache.commons.io.FileUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.Assert.assertEquals; public class FileLineQuerySourceTest { + private record SourceConfig(int size, String lineEnding, boolean overshoot) { + @Override + public String toString() { + return "{ size: " + size + ", overshoot: " + overshoot + ", line_ending: " + StringEscapeUtils.escapeJava(lineEnding) + " }"; + } + } - private static final String PATH = "src/test/resources/query/source/queries.txt"; + private final static Function queryTemplate = (i) -> "Query " + i + " {still query " + i + "}"; - private final FileLineQuerySource querySource; + private static Path directory; + private static List cachedArguments = null; - public FileLineQuerySourceTest() { - this.querySource = new FileLineQuerySource(PATH); + private static String createFileContent(int size, String lineEnding, boolean overshoot) { + final var stringBuilder = new StringBuilder(); + int limit = overshoot ? size : size - 1; + for (int i = 0; i < limit; i++) { + stringBuilder.append(queryTemplate.apply(i)).append(lineEnding); + } + if (!overshoot) { + stringBuilder.append(queryTemplate.apply(size - 1)); + } + return stringBuilder.toString(); } - @Test - public void sizeTest() { - assertEquals(3, this.querySource.size()); + public static List createTestSource() throws IOException { + if (cachedArguments != null) { + return cachedArguments; + } + List output = new ArrayList<>(); + int[] sizes = { 1, 1000 }; + String[] lineEndings = { "\n", "\r\n", "\r" }; + boolean[] overshoots = { false, true }; + for (int size : sizes) { + for (String lineEnding : lineEndings) { + for (boolean overshoot : overshoots) { + final var fileContent = createFileContent(size, lineEnding, overshoot); + final var filePath = Files.createTempFile(directory, "Query", ".txt"); + Files.writeString(filePath, fileContent); + final var querySource = new FileLineQuerySource(filePath); + output.add(Arguments.of(querySource, new SourceConfig(size, lineEnding, overshoot))); + } + } + } + cachedArguments = output; + return output; } - @Test - public void getQueryTest() throws IOException { - assertEquals("QUERY 1 {still query 1}", this.querySource.getQuery(0)); - assertEquals("QUERY 2 {still query 2}", this.querySource.getQuery(1)); - assertEquals("QUERY 3 {still query 3}", this.querySource.getQuery(2)); + @BeforeAll + public static void createTempDirectory() throws IOException { + directory = Files.createTempDirectory("iguana-file-line-query-source-test-dir"); + } + + @AfterAll + public static void deleteTempDirectory() throws IOException { + FileUtils.deleteDirectory(directory.toFile()); } @Test - public void getAllQueriesTest() throws IOException { - List expected = new ArrayList<>(3); - expected.add("QUERY 1 {still query 1}"); - expected.add("QUERY 2 {still query 2}"); - expected.add("QUERY 3 {still query 3}"); + public void testInitialization() throws IOException { + assertThrows(NullPointerException.class, () -> new FileLineQuerySource(null)); + assertDoesNotThrow(() -> new FileLineQuerySource(Files.createTempFile(directory, "Query", ".txt"))); + final var notEmptyFile = Files.createTempFile(directory, "Query", ".txt"); + Files.writeString(notEmptyFile, "not empty"); + assertDoesNotThrow(() -> new FileLineQuerySource(notEmptyFile)); + } - assertEquals(expected, this.querySource.getAllQueries()); + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void sizeTest(FileLineQuerySource querySource, SourceConfig config) throws IOException { + assertEquals(config.size, querySource.size()); } - @Test - public void getHashcodeTest() { - int expected = FileUtils.getHashcodeFromFileContent(PATH); - assertEquals(expected, this.querySource.hashCode()); + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void getQueryTest(FileLineQuerySource querySource, SourceConfig config) throws IOException { + for (int i = 0; i < config.size; i++) { + assertEquals(queryTemplate.apply(i), querySource.getQuery(i)); + } + } + + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void getAllQueriesTest(FileLineQuerySource querySource, SourceConfig config) throws IOException { + List expected = IntStream.range(0, config.size).mapToObj(i -> queryTemplate.apply(i)).toList(); + assertEquals(expected, querySource.getAllQueries()); + } + + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void getHashcodeTest(FileLineQuerySource querySource, SourceConfig config) { + assertTrue(querySource.hashCode() != 0); } } \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySourceTest.java b/src/test/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySourceTest.java index 07acffe18..0b90506d2 100644 --- a/src/test/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySourceTest.java +++ b/src/test/java/org/aksw/iguana/cc/query/source/impl/FileSeparatorQuerySourceTest.java @@ -1,70 +1,129 @@ package org.aksw.iguana.cc.query.source.impl; -import org.aksw.iguana.cc.utils.FileUtils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.apache.commons.io.FileUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.Assert.assertEquals; -@RunWith(Parameterized.class) public class FileSeparatorQuerySourceTest { + private record SourceConfig(int size, String lineEnding, boolean overshoot, String separator) { + @Override + public String toString() { + return "{ size: " + size + + ", overshoot: " + overshoot + + ", line_ending: " + StringEscapeUtils.escapeJava(lineEnding) + + ", separator: " + StringEscapeUtils.escapeJava(separator) + " }"; + } + } - private final FileSeparatorQuerySource querySource; + private final static BiFunction queryTemplate = (i, le) -> "Query " + i + " {" + le + "still query " + i + le + "}"; - private final String path; + private static Path directory; + private static List cachedArguments = null; - public FileSeparatorQuerySourceTest(String path, String separator) { - this.path = path; + private static String createFileContent(int size, String lineEnding, boolean overshoot, String separator) { + final var stringBuilder = new StringBuilder(); + int limit = overshoot ? size : size - 1; + for (int i = 0; i < limit; i++) { + stringBuilder.append(queryTemplate.apply(i, lineEnding)).append(separator); + } + if (!overshoot) { + stringBuilder.append(queryTemplate.apply(size - 1, lineEnding)); + } + return stringBuilder.toString(); + } - if (separator == null) { - this.querySource = new FileSeparatorQuerySource(this.path); - } else { - this.querySource = new FileSeparatorQuerySource(this.path, separator); + public static List createTestSource() throws IOException { + if (cachedArguments != null) { + return cachedArguments; + } + List output = new ArrayList<>(); + int[] sizes = { 1, 1000 }; + String[] lineEndings = { "\n", "\r\n", "\r" }; + boolean[] overshoots = { false, true }; // tests if there is no empty query at the end + String[] separators = { "\n\t\t", "\n###\n", "###", ""}; + for (int size : sizes) { + for (String lineEnding : lineEndings) { + for (boolean overshoot : overshoots) { + for (String separator : separators) { + String fileContent; + if (separator.isEmpty()) + fileContent = createFileContent(size, lineEnding, overshoot, lineEnding + lineEnding); // make empty lines + else + fileContent = createFileContent(size, lineEnding, overshoot, separator); + final var filePath = Files.createTempFile(directory, "Query", ".txt"); + Files.writeString(filePath, fileContent); + FileSeparatorQuerySource querySource; + if (separator.equals("###")) + querySource = new FileSeparatorQuerySource(filePath); // test default separator + else + querySource = new FileSeparatorQuerySource(filePath, separator); + output.add(Arguments.of(querySource, new SourceConfig(size, lineEnding, overshoot, separator))); + } + } + } } + cachedArguments = output; + return output; } - @Parameterized.Parameters - public static Collection data() { - Collection testData = new ArrayList<>(); - testData.add(new Object[]{"src/test/resources/query/source/separated-queries-default.txt", null}); - testData.add(new Object[]{"src/test/resources/query/source/separated-queries-space.txt", ""}); + @BeforeAll + public static void createTempDirectory() throws IOException { + directory = Files.createTempDirectory("iguana-file-line-query-source-test-dir"); + } - return testData; + @AfterAll + public static void deleteTempDirectory() throws IOException { + FileUtils.deleteDirectory(directory.toFile()); } @Test - public void sizeTest() { - assertEquals(3, this.querySource.size()); + public void testInitialization() throws IOException { + assertThrows(NullPointerException.class, () -> new FileSeparatorQuerySource(null)); + assertDoesNotThrow(() -> new FileSeparatorQuerySource(Files.createTempFile(directory, "Query", ".txt"), "###")); + final var notEmptyFile = Files.createTempFile(directory, "Query", ".txt"); + Files.writeString(notEmptyFile, "not empty"); + assertDoesNotThrow(() -> new FileSeparatorQuerySource(notEmptyFile)); + assertDoesNotThrow(() -> new FileSeparatorQuerySource(notEmptyFile, "\n\n\n")); } - @Test - public void getQueryTest() throws IOException { - String le = FileUtils.getLineEnding(this.path); - assertEquals("QUERY 1 {" + le + "still query 1" + le + "}", this.querySource.getQuery(0)); - assertEquals("QUERY 2 {" + le + "still query 2" + le + "}", this.querySource.getQuery(1)); - assertEquals("QUERY 3 {" + le + "still query 3" + le + "}", this.querySource.getQuery(2)); + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void sizeTest(FileSeparatorQuerySource querySource, SourceConfig config) throws IOException { + assertEquals(config.size, querySource.size()); } - @Test - public void getAllQueriesTest() throws IOException { - List expected = new ArrayList<>(3); - String le = FileUtils.getLineEnding(this.path); - expected.add("QUERY 1 {" + le + "still query 1" + le + "}"); - expected.add("QUERY 2 {" + le + "still query 2" + le + "}"); - expected.add("QUERY 3 {" + le + "still query 3" + le + "}"); - - assertEquals(expected, this.querySource.getAllQueries()); + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void getQueryTest(FileSeparatorQuerySource querySource, SourceConfig config) throws IOException { + for (int i = 0; i < config.size; i++) { + assertEquals(queryTemplate.apply(i, config.lineEnding), querySource.getQuery(i)); + } } - @Test - public void getHashcodeTest() { - int expected = FileUtils.getHashcodeFromFileContent(this.path); - assertEquals(expected, this.querySource.hashCode()); + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void getAllQueriesTest(FileSeparatorQuerySource querySource, SourceConfig config) throws IOException { + List expected = IntStream.range(0, config.size).mapToObj(i -> queryTemplate.apply(i, config.lineEnding)).toList(); + assertEquals(expected, querySource.getAllQueries()); + } + + @ParameterizedTest(name = "[{index}] config = {1}") + @MethodSource("createTestSource") + public void getHashcodeTest(FileSeparatorQuerySource querySource, SourceConfig config) { + assertTrue(querySource.hashCode() != 0); } } \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySourceTest.java b/src/test/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySourceTest.java index ef58c6101..811cd26c5 100644 --- a/src/test/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySourceTest.java +++ b/src/test/java/org/aksw/iguana/cc/query/source/impl/FolderQuerySourceTest.java @@ -1,8 +1,10 @@ package org.aksw.iguana.cc.query.source.impl; -import org.junit.*; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -11,35 +13,12 @@ import java.util.*; import java.util.stream.Collectors; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(Parameterized.class) public class FolderQuerySourceTest { + static Path tempDir; - Path tempDir; - TestConfig testConfig; - - public FolderQuerySourceTest(TestConfig testConfig) { - this.testConfig = testConfig; - } - - public static class TestConfig { - - public TestConfig(int numberOfQueries) { - this.numberOfQueries = numberOfQueries; - } - - int numberOfQueries; - } - - public static class Query implements Comparable { - public Query(Path queryFile, String content) { - this.queryFile = queryFile; - this.content = content; - } - - Path queryFile; - String content; + public record Query(Path queryFile, String content) implements Comparable { @Override public int compareTo(Query other) { @@ -47,47 +26,47 @@ public int compareTo(Query other) { } } - List queries; - - - @Parameterized.Parameters - public static Collection data() { - return List.of(new TestConfig(0), - new TestConfig(1), - new TestConfig(2), - new TestConfig(5)); + public static List data() throws IOException { + final var sizes = List.of(1, 2, 10, 100, 1000); + final var out = new ArrayList(); + for (int size : sizes) { + final var queries = new LinkedList(); + final var queryDir = Files.createTempDirectory(tempDir, "query-dir"); + for (int i = 0; i < size; i++) { + final Path queryFile = Files.createTempFile(queryDir, "Query", ".txt"); + final String content = UUID.randomUUID().toString(); + Files.write(queryFile, content.getBytes(StandardCharsets.UTF_8)); + queries.add(new Query(queryFile, content)); + } + // Queries in the folder are expected in alphabetic order of the file names. + Collections.sort(queries); + out.add(Arguments.of(queryDir, queries)); + } + return out; } - @Before - public void createFolder() throws IOException { - this.tempDir = Files.createTempDirectory("folder-query-source-test-dir"); - - this.queries = new LinkedList<>(); - for (int i = 0; i < testConfig.numberOfQueries; i++) { - final Path queryFile = Files.createTempFile(tempDir, "Query", ".txt"); - final String content = UUID.randomUUID().toString(); - Files.write(queryFile, content.getBytes(StandardCharsets.UTF_8)); - this.queries.add(new Query(queryFile, content)); - } - // Queries in the folder are expected in alphabetic order of the file names. - Collections.sort(this.queries); + @BeforeAll + public static void createFolder() throws IOException { + tempDir = Files.createTempDirectory("iguana-folder-query-source-test-dir"); } - @After - public void removeFolder() throws IOException { - org.apache.commons.io.FileUtils.deleteDirectory(this.tempDir.toFile()); + + @AfterAll + public static void removeFolder() throws IOException { + org.apache.commons.io.FileUtils.deleteDirectory(tempDir.toFile()); } - @Test - public void testFolderQuerySource() throws IOException { - FolderQuerySource querySource = new FolderQuerySource(tempDir.toString()); + @ParameterizedTest + @MethodSource("data") + public void testFolderQuerySource(Path tempDir, List expectedQueries) throws IOException { + FolderQuerySource querySource = new FolderQuerySource(tempDir); - assertEquals(this.queries.size(), querySource.size()); + assertEquals(expectedQueries.size(), querySource.size()); for (int i = 0; i < querySource.size(); i++) { - assertEquals(queries.get(i).content, querySource.getQuery(i)); + assertEquals(expectedQueries.get(i).content, querySource.getQuery(i)); } - assertEquals(queries.stream().map(q -> q.content).collect(Collectors.toList()), querySource.getAllQueries()); + assertEquals(expectedQueries.stream().map(q -> q.content).collect(Collectors.toList()), querySource.getAllQueries()); } } \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/storage/impl/CSVStorageTest.java b/src/test/java/org/aksw/iguana/cc/storage/impl/CSVStorageTest.java new file mode 100644 index 000000000..db1d5ff5f --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/storage/impl/CSVStorageTest.java @@ -0,0 +1,140 @@ +package org.aksw.iguana.cc.storage.impl; + +import com.opencsv.CSVReader; +import com.opencsv.exceptions.CsvException; +import org.aksw.iguana.cc.mockup.MockupQueryHandler; +import org.aksw.iguana.cc.mockup.MockupWorker; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class CSVStorageTest extends StorageTest { + private static final String EXPECTED_FILES_DIR = "src/test/resources/test-data/csv-storage-test/"; + + public static List data() { + final var workersTask1 = List.of( + MockupWorker.createWorkers(0, 2, new MockupQueryHandler(0, 10), "test-connection-1", "v1.0.0", "test-dataset-1"), + MockupWorker.createWorkers(2, 2, new MockupQueryHandler(1, 10), "test-connection-2", "v1.1.0", "test-dataset-2") + ); + + final var workersTask2 = List.of( + MockupWorker.createWorkers(0, 2, new MockupQueryHandler(2, 5), "test-connection-3", "v1.2.0", "test-dataset-3"), + MockupWorker.createWorkers(2, 2, new MockupQueryHandler(3, 5), "test-connection-4", "v1.3.0", "test-dataset-4") + ); + + return List.of(Arguments.of(List.of(createTaskResult(workersTask1, 0, "123"), createTaskResult(workersTask2, 1, "123")))); + } + + @ParameterizedTest + @MethodSource("data") + protected void testCSVStorage(List results) throws IOException { + final var storage = new CSVStorage(tempDir.toString(), getMetrics(), "123"); + for (var result : results) + storage.storeResult(result.resultModel()); + + final var expectedFiles = Path.of(EXPECTED_FILES_DIR); + final var actualFiles = tempDir; + + try (var files = Files.list(expectedFiles)) { + files.forEach( + x -> { + try { + compareFile(x, actualFiles.resolve(x.getFileName())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + ); + } + + storage.storeData(new TestStorable()); + final var path = tempDir.resolve("suite-123").resolve("task-1").resolve("csv-folder").toFile(); + assertTrue(path.exists()); + assertTrue(path.isDirectory()); + assertEquals(2, path.listFiles().length); + for (var file : path.listFiles()) { + if (file.getName().equals("csv-file-1.csv")) + assertEquals(2, Files.readAllLines(file.toPath()).size()); + else if (file.getName().equals("csv-file-2.csv")) + assertEquals(3, Files.readAllLines(file.toPath()).size()); + else + throw new RuntimeException("Unexpected file name: " + file.getName()); + } + } + + private void compareFile(Path expected, Path actual) throws IOException { + if (Files.isDirectory(expected)) { + assertTrue(Files.isDirectory(actual), String.format("Expected directory %s but found file %s", expected, actual)); + assertEquals(expected.getFileName(), actual.getFileName()); + try (var files = Files.list(expected)) { + files.forEach(x -> { + try { + compareFile(x, actual.resolve(x.getFileName())); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } else if (Files.isRegularFile(expected)) { + assertTrue(Files.isRegularFile(actual), String.format("Expected file %s but found directory %s", expected, actual)); + assertEquals(expected.getFileName(), actual.getFileName()); + compareCSVFiles(expected, actual); + } else { + throw new RuntimeException(String.format("Expected file or directory %s but found nothing", expected)); + } + } + + private void compareCSVFiles(Path expected, Path actual) throws IOException { + try (CSVReader readerExpected = new CSVReader(new FileReader(expected.toFile())); + CSVReader readerActual = new CSVReader(new FileReader(actual.toFile()))) { + + String[] headerExpected; + String[] headerActual; + try { + headerExpected = readerExpected.readNext(); + headerActual = readerActual.readNext(); + } catch (CsvException e) { + throw new RuntimeException(String.format("CSV format in the header of file %s is malformed.", expected), e); + } + + assertEquals(headerExpected.length, headerActual.length, String.format("Headers don't match. Actual: %s, Expected: %s", Arrays.toString(headerActual), Arrays.toString(headerExpected))); + for (int i = 0; i < headerExpected.length; i++) { + assertEquals(headerExpected[i], headerActual[i], String.format("Headers don't match. Actual: %s, Expected: %s", Arrays.toString(headerActual), Arrays.toString(headerExpected))); + } + + List expectedValues; + List actualValues; + + try { + expectedValues = new ArrayList<>(readerExpected.readAll()); + actualValues = new ArrayList<>(readerActual.readAll()); + } catch (CsvException e) { + throw new RuntimeException(String.format("CSV format in file %s is malformed.", expected), e); + } + + for (String[] expectedLine : expectedValues) { + List sameLines = actualValues.stream().filter(x -> { + for (int i = 0; i < expectedLine.length; i++) { + if (!expectedLine[i].equals(x[i])) return false; + } + return true; + }).toList(); + + assertFalse(sameLines.isEmpty(), String.format("Line (%s) not found in actual file", Arrays.toString(expectedLine))); + actualValues.remove(sameLines.get(0)); + } + assertTrue(actualValues.isEmpty(), String.format("Actual file contains more lines than expected. Lines: %s", actualValues.stream().map(x -> "[" + String.join(", ", x) + "]").collect(Collectors.joining("\n")))); + } + } +} diff --git a/src/test/java/org/aksw/iguana/cc/storage/impl/RDFFileStorageTest.java b/src/test/java/org/aksw/iguana/cc/storage/impl/RDFFileStorageTest.java new file mode 100644 index 000000000..e044ecfbc --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/storage/impl/RDFFileStorageTest.java @@ -0,0 +1,56 @@ +package org.aksw.iguana.cc.storage.impl; + +import org.aksw.iguana.cc.mockup.MockupQueryHandler; +import org.aksw.iguana.cc.mockup.MockupWorker; +import org.apache.jena.rdf.model.*; +import org.apache.jena.riot.RDFDataMgr; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; +import java.util.List; + +/** + * This Test class extends the StorageTest class and tests the RDFFileStorage class. + */ +public class RDFFileStorageTest extends StorageTest { + public static List data() { + final var arguments = new ArrayList(); + + final var paths = new ArrayList<>(List.of("rdf-file-storage-test1.ttl", "rdf-file-storage-test1.nt", "rdf-file-storage-test1.nt", "")); + + final var queryHandler1 = new MockupQueryHandler(0, 10); + final var queryHandler2 = new MockupQueryHandler(1, 10); + + final var workers = List.of( + MockupWorker.createWorkers(0, 2, queryHandler1, "test-connection-1", "v1.0.0", "test-dataset-1"), + MockupWorker.createWorkers(2, 2, queryHandler2, "test-connection-1", "v1.0.0", "test-dataset-1") + ); + final var task1 = createTaskResult(workers, 0, "0"); + final var task2 = createTaskResult(workers, 1, "0"); + + // test file creation + for (String path : paths) { + arguments.add(Arguments.of(tempDir.resolve(path).toString(), List.of(task1), task1.resultModel())); + } + + // test file appending + Model concatenatedModel = ModelFactory.createDefaultModel().add(task1.resultModel()).add(task2.resultModel()); + arguments.add(Arguments.of(tempDir.resolve("rdf-file-storage-test2.ttl").toString(), List.of(task1, task2), concatenatedModel)); + return arguments; + } + + @ParameterizedTest + @MethodSource("data") + public void testRDFFileStorage(String path, List results, Model expectedModel) { + final var rdfStorage = new RDFFileStorage(path); + for (var result : results) { + rdfStorage.storeResult(result.resultModel()); + } + path = rdfStorage.getFileName(); + Model actualModel = RDFDataMgr.loadModel(path); + Assertions.assertTrue(actualModel.isIsomorphicWith(expectedModel)); + } +} diff --git a/src/test/java/org/aksw/iguana/cc/storage/impl/StorageTest.java b/src/test/java/org/aksw/iguana/cc/storage/impl/StorageTest.java new file mode 100644 index 000000000..6fe07a015 --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/storage/impl/StorageTest.java @@ -0,0 +1,120 @@ +package org.aksw.iguana.cc.storage.impl; + +import org.aksw.iguana.cc.lang.LanguageProcessor; +import org.aksw.iguana.cc.metrics.Metric; +import org.aksw.iguana.cc.metrics.impl.*; +import org.aksw.iguana.cc.mockup.MockupWorker; +import org.aksw.iguana.cc.storage.Storable; +import org.aksw.iguana.cc.storage.Storage; +import org.aksw.iguana.cc.mockup.MockupStorage; +import org.aksw.iguana.cc.tasks.impl.StresstestResultProcessor; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.apache.commons.io.FileUtils; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.function.Supplier; + + +public abstract class StorageTest { + @BeforeAll + public static void createFolder() throws IOException { + tempDir = Files.createTempDirectory("iguana-storage-test-dir"); + } + + @AfterAll + public static void deleteFolder() throws IOException { + FileUtils.deleteDirectory(tempDir.toFile()); + } + + public static class TestStorable implements Storable.AsCSV, Storable.AsRDF { + + @Override + public Storable.CSVData toCSV() { + final var data = new Storable.CSVData("csv-folder", List.of( + new Storable.CSVData.CSVFileData("csv-file-1", new String[][]{{"header-1", "header-2"}, {"randomString", "100"}}), + new Storable.CSVData.CSVFileData("csv-file-2", new String[][]{{"header-1", "header-2"}, {"randomString-2", "200"}, {"randomString-3", "300"}}) + )); + return data; + } + + @Override + public Model toRDF() { + Model m = ModelFactory.createDefaultModel(); + m.add(m.createResource("http://example.org/subject"), m.createProperty("http://example.org/predicate"), m.createResource("http://example.org/object")); + return m; + } + } + + @BeforeEach + public void resetDate() { + someDateTime = GregorianCalendar.from(ZonedDateTime.ofInstant(Instant.parse("2023-10-21T20:48:06.399Z"), ZoneId.of("Europe/Berlin"))); + } + + protected record TaskResult(Model resultModel, List workerResults) {} + + protected static Path tempDir; + + private static Calendar someDateTime = GregorianCalendar.from(ZonedDateTime.ofInstant(Instant.parse("2023-10-21T20:48:06.399Z"), ZoneId.of("Europe/Berlin"))); + + private static Calendar getDateTime() { + someDateTime.add(Calendar.MINUTE, 1); + someDateTime.add(Calendar.SECOND, 18); + return someDateTime; + } + + public static List getMetrics() { + return List.of( + new AggregatedExecutionStatistics(), + new AvgQPS(), + new EachExecutionStatistic(), + new NoQ(), + new NoQPH(), + new PAvgQPS(1000), + new PQPS(1000), + new QMPH(), + new QPS() + ); + } + + // Argument is a List that contains lists of workers with the same configuration. + protected static TaskResult createTaskResult(List> workers, int taskID, String suiteID) { + final var queryIDs = new ArrayList(); + for (var list : workers) { + queryIDs.addAll(List.of(list.get(0).config().queries().getAllQueryIds())); + } + + final var metrics = getMetrics(); + final var storages = new ArrayList(); + final Supplier>> supplier = HashMap::new; + + final var ls = new MockupStorage(); + storages.add(ls); + + final var flatWorkerList = workers.stream().flatMap(Collection::stream).toList(); + + final var srp = new StresstestResultProcessor(suiteID, taskID, flatWorkerList, queryIDs, metrics, storages, supplier); + + final var workerResults = new ArrayList(); + for (var list : workers) { + workerResults.addAll(MockupWorker.createWorkerResults(list.get(0).config().queries(), list)); + } + + srp.process(workerResults); + Calendar startTime = (Calendar) getDateTime().clone(); + srp.calculateAndSaveMetrics(startTime, getDateTime()); + + return new TaskResult(ls.getResultModel(), workerResults); + } + +} diff --git a/src/test/java/org/aksw/iguana/cc/storage/impl/TriplestoreStorageTest.java b/src/test/java/org/aksw/iguana/cc/storage/impl/TriplestoreStorageTest.java new file mode 100644 index 000000000..a33d135cf --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/storage/impl/TriplestoreStorageTest.java @@ -0,0 +1,70 @@ +package org.aksw.iguana.cc.storage.impl; + +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import org.aksw.iguana.cc.mockup.MockupQueryHandler; +import org.aksw.iguana.cc.mockup.MockupWorker; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.update.UpdateFactory; +import org.apache.jena.update.UpdateRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.List; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TriplestoreStorageTest extends StorageTest { + + @RegisterExtension + public static WireMockExtension wm = WireMockExtension.newInstance() + .options(new WireMockConfiguration().useChunkedTransferEncoding(Options.ChunkedEncodingPolicy.NEVER).dynamicPort().notifier(new Slf4jNotifier(true))) + .failOnUnmatchedRequests(true) + .build(); + + @Test + public void dataTest() throws URISyntaxException { + final var uuid = UUID.randomUUID(); + wm.stubFor(post(urlEqualTo("/ds/sparql")) + .willReturn(aResponse() + .withStatus(200))) + .setId(uuid); + + final List> worker = List.of(List.of( + new MockupWorker( + 0, + new MockupQueryHandler(1, 2), + "conneciton", + "v2", + "wikidata", + Duration.ofSeconds(2)) + )); + final var testData = createTaskResult(worker, 0, "1"); + + final var uri = new URI("http://localhost:" + wm.getPort() + "/ds/sparql"); + final var storage = new TriplestoreStorage(String.valueOf(uri)); + storage.storeResult(testData.resultModel()); + + List allServeEvents = wm.getAllServeEvents(); + ServeEvent request = allServeEvents.get(0); + String body = request.getRequest().getBodyAsString(); + + StringWriter nt = new StringWriter(); + RDFDataMgr.write(nt, testData.resultModel(), org.apache.jena.riot.Lang.NTRIPLES); + + UpdateRequest updateRequestActual = UpdateFactory.create(body); + UpdateRequest updateRequestExpected = UpdateFactory.create().add("INSERT DATA { " + nt + " }"); + + assertTrue(updateRequestExpected.iterator().next().equalTo(updateRequestActual.iterator().next(), null)); + } +} diff --git a/src/test/java/org/aksw/iguana/cc/suite/IguanaSuiteParserTest.java b/src/test/java/org/aksw/iguana/cc/suite/IguanaSuiteParserTest.java new file mode 100644 index 000000000..9ab39b009 --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/suite/IguanaSuiteParserTest.java @@ -0,0 +1,36 @@ +package org.aksw.iguana.cc.suite; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +class IguanaSuiteParserTest { + + public static Stream validData() throws IOException { + final var dir = Path.of("./src/test/resources/suite-configs/valid/"); + return Files.list(dir).map(Arguments::of); + } + + public static Stream invalidData() throws IOException { + final var dir = Path.of("./src/test/resources/suite-configs/invalid/"); + return Files.list(dir).map(Arguments::of); + } + + @ParameterizedTest + @MethodSource("validData") + public void testValidDeserialization(Path config) throws IOException { + Assertions.assertTrue(IguanaSuiteParser.validateConfig(config)); + } + + @ParameterizedTest + @MethodSource("invalidData") + public void testInvalidDeserialization(Path config) throws IOException { + Assertions.assertFalse(IguanaSuiteParser.validateConfig(config)); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/tasks/MockupStorage.java b/src/test/java/org/aksw/iguana/cc/tasks/MockupStorage.java deleted file mode 100644 index 145a72570..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/MockupStorage.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.aksw.iguana.cc.tasks; - -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; - -public class MockupStorage implements Storage { - private Model m = ModelFactory.createDefaultModel(); - - @Override - public void storeResult(Model data) { - m.add(data); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/MockupTask.java b/src/test/java/org/aksw/iguana/cc/tasks/MockupTask.java deleted file mode 100644 index a077916b8..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/MockupTask.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.aksw.iguana.cc.tasks; - - -public class MockupTask extends AbstractTask{ - - public MockupTask(String empty){ - } - - - @Override - public void execute() { - } - - -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/ServerMock.java b/src/test/java/org/aksw/iguana/cc/tasks/ServerMock.java deleted file mode 100644 index b70b0d4a4..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/ServerMock.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.aksw.iguana.cc.tasks; - -import org.simpleframework.http.Request; -import org.simpleframework.http.Response; -import org.simpleframework.http.Status; -import org.simpleframework.http.core.Container; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -/** - * Server Mock representing a TS - * - * @author f.conrads - * - */ -public class ServerMock implements Container { - - private static final Logger LOGGER = LoggerFactory.getLogger(ServerMock.class); - private String actualContent; - - - @Override - public void handle(Request request, Response resp) { - String content=null; - try { - content = request.getContent(); - } catch (IOException e) { - LOGGER.error("Got exception.", e); - } - this.actualContent=content; - resp.setCode(Status.OK.code); - try { - resp.getOutputStream().close(); - } catch (IOException e) { - LOGGER.error("Could not close Response Output Stream"); - } - } - - /** - * @return the actualContent - */ - public String getActualContent() { - return actualContent; - } - - /** - * @param actualContent the actualContent to set - */ - public void setActualContent(String actualContent) { - this.actualContent = actualContent; - } - -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/CSVStorageTest.java b/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/CSVStorageTest.java deleted file mode 100644 index 054d6e347..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/CSVStorageTest.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.aksw.iguana.cc.tasks.storage.impl; - -import com.opencsv.CSVReader; -import com.opencsv.exceptions.CsvException; -import org.aksw.iguana.cc.config.IguanaConfig; -import org.aksw.iguana.cc.tasks.stresstest.metrics.*; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.AggregatedExecutionStatistics; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.NoQ; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.NoQPH; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.QPS; -import org.aksw.iguana.cc.tasks.stresstest.storage.impl.CSVStorage; -import org.aksw.iguana.commons.rdf.IONT; -import org.aksw.iguana.commons.rdf.IPROP; -import org.aksw.iguana.commons.rdf.IRES; -import org.apache.commons.io.FileUtils; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; -import org.junit.jupiter.api.*; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.io.FileReader; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -public class CSVStorageTest { - CSVStorage storage; - Path folder; - Path suiteFolder; - - @BeforeEach - public void setup() throws IOException { - this.folder = Files.createTempDirectory("iguana-CSVStorage-test"); - this.suiteFolder = folder.resolve(IguanaConfig.getSuiteID()); - } - - public static Arguments createTestData1() { - // Entry records should store metric values in the same order as the metrics in the following list. - List metrics = List.of(new NoQ(), new NoQPH(), new QPS(), new AggregatedExecutionStatistics()); - - // First Task has 2 Worker - Resource datasetRes = IRES.getResource("dataset1"); - Resource experimentRes = IRES.getResource("suite/experiment"); - Resource taskRes = IRES.getResource("suite/experiment/task1"); - Resource conRes = IRES.getResource("triplestore1"); - Resource workerRes1 = IRES.getResource("worker1"); - Resource workerRes2 = IRES.getResource("worker2"); - Resource taskQueryRes = IRES.getResource("task-query"); - Resource workerQueryRes1 = IRES.getResource("worker-query-1"); - Resource workerQueryRes2 = IRES.getResource("worker-query-2"); - Resource queryRes1 = IRES.getResource("query1"); - Resource queryRes2 = IRES.getResource("query2"); - - Model m = ModelFactory.createDefaultModel(); - - m.add(experimentRes, RDF.type, IONT.experiment); - m.add(experimentRes, IPROP.dataset, datasetRes); - m.add(experimentRes, IPROP.task, taskRes); - m.add(datasetRes, RDFS.label, ResourceFactory.createTypedLiteral("dataset1")); - m.add(datasetRes, RDF.type, IONT.dataset); - m.add(conRes, RDF.type, IONT.connection); - m.add(conRes, RDFS.label, ResourceFactory.createTypedLiteral("triplestore1")); - m.add(conRes, IPROP.version, ResourceFactory.createTypedLiteral("v1")); - m.add(taskRes, RDF.type, IONT.task); - m.add(taskRes, IPROP.connection, conRes); - m.add(taskRes, IPROP.startDate, ResourceFactory.createTypedLiteral("now")); - m.add(taskRes, IPROP.endDate, ResourceFactory.createTypedLiteral("then")); - m.add(taskRes, IPROP.workerResult, workerRes1); - m.add(taskRes, IPROP.workerResult, workerRes2); - m.add(taskRes, IPROP.noOfWorkers, ResourceFactory.createTypedLiteral(2)); - m.add(taskRes, IPROP.createMetricProperty(new NoQ()), ResourceFactory.createTypedLiteral(BigInteger.valueOf(2))); - m.add(taskRes, IPROP.createMetricProperty(new NoQPH()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(20,2))); - m.add(taskRes, IPROP.query, taskQueryRes); - - m.add(workerRes1, RDF.type, IONT.worker); - m.add(workerRes1, IPROP.workerID, ResourceFactory.createTypedLiteral(BigInteger.valueOf(1))); - m.add(workerRes1, IPROP.workerType, ResourceFactory.createTypedLiteral("SPARQL")); - m.add(workerRes1, IPROP.noOfQueries, ResourceFactory.createTypedLiteral(BigInteger.valueOf(2))); - m.add(workerRes1, IPROP.timeOut, ResourceFactory.createTypedLiteral(BigInteger.valueOf(100))); - m.add(workerRes1, IPROP.createMetricProperty(new NoQ()), ResourceFactory.createTypedLiteral(BigInteger.valueOf(8))); - m.add(workerRes1, IPROP.createMetricProperty(new NoQPH()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(108))); - m.add(workerRes1, IPROP.query, workerQueryRes1); - - m.add(workerRes2, RDF.type, IONT.worker); - m.add(workerRes2, IPROP.workerID, ResourceFactory.createTypedLiteral(BigInteger.valueOf(2))); - m.add(workerRes2, IPROP.workerType, ResourceFactory.createTypedLiteral("LQRAPS")); - m.add(workerRes2, IPROP.noOfQueries, ResourceFactory.createTypedLiteral(BigInteger.valueOf(1))); - m.add(workerRes2, IPROP.timeOut, ResourceFactory.createTypedLiteral(BigInteger.valueOf(50))); - m.add(workerRes2, IPROP.createMetricProperty(new NoQ()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(0))); - m.add(workerRes2, IPROP.createMetricProperty(new NoQPH()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(0))); - m.add(workerRes2, IPROP.query, workerQueryRes2); - - m.add(taskQueryRes, IPROP.createMetricProperty(new QPS()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(72000, 2))); - m.add(taskQueryRes, IPROP.succeeded, ResourceFactory.createTypedLiteral(BigInteger.valueOf(2))); - m.add(taskQueryRes, IPROP.failed, ResourceFactory.createTypedLiteral(BigInteger.valueOf(0))); - m.add(taskQueryRes, IPROP.totalTime, ResourceFactory.createTypedLiteral(BigInteger.valueOf(12345))); - m.add(taskQueryRes, IPROP.resultSize, ResourceFactory.createTypedLiteral(BigInteger.valueOf(1000))); - m.add(taskQueryRes, IPROP.wrongCodes, ResourceFactory.createTypedLiteral(BigInteger.valueOf(0))); - m.add(taskQueryRes, IPROP.timeOuts, ResourceFactory.createTypedLiteral(BigInteger.valueOf(0))); - m.add(taskQueryRes, IPROP.unknownException, ResourceFactory.createTypedLiteral(BigInteger.valueOf(0))); - m.add(taskQueryRes, IPROP.queryID, queryRes1); - m.add(taskQueryRes, IPROP.createMetricProperty(new QPS()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(72000, 2))); - - m.add(workerQueryRes1, IPROP.createMetricProperty(new QPS()), ResourceFactory.createTypedLiteral(BigDecimal.valueOf(10))); - m.add(workerQueryRes1, IPROP.succeeded, ResourceFactory.createTypedLiteral(BigInteger.valueOf(1))); - m.add(workerQueryRes1, IPROP.failed, ResourceFactory.createTypedLiteral(BigInteger.valueOf(2))); - m.add(workerQueryRes1, IPROP.totalTime, ResourceFactory.createTypedLiteral(BigInteger.valueOf(100))); - m.add(workerQueryRes1, IPROP.resultSize, ResourceFactory.createTypedLiteral(BigInteger.valueOf(98))); - m.add(workerQueryRes1, IPROP.wrongCodes, ResourceFactory.createTypedLiteral(BigInteger.valueOf(3))); - m.add(workerQueryRes1, IPROP.timeOuts, ResourceFactory.createTypedLiteral(BigInteger.valueOf(4))); - m.add(workerQueryRes1, IPROP.unknownException, ResourceFactory.createTypedLiteral(BigInteger.valueOf(5))); - m.add(workerQueryRes1, IPROP.queryID, queryRes1); - - // workerQueryRes2 isn't complete, therefore won't be saved - m.add(workerQueryRes2, IPROP.queryID, queryRes2); - - Path testFileFolder = Path.of("src/test/resources/storage/csv_test_files/"); - - return Arguments.of(Named.of(String.format("One simple tasks with one faulty entry. | ExpectedFolder: %s | Metrics: %s", testFileFolder, metrics.stream().map(Metric::getAbbreviation).toList()), new Suite(List.of(m), metrics, testFileFolder))); - } - - @AfterEach - public void cleanup() throws IOException { - FileUtils.deleteDirectory(this.folder.toFile()); - } - - public static Stream data() { - return Stream.of( - createTestData1() - ); - } - - @ParameterizedTest - @MethodSource("data") - @DisplayName("Test CSVStorage") - public void testCSVStorage(Suite suite) throws IOException { - for (Model m : suite.taskResults) { - // Metrics need to be set here, because the CSVStorage uses the manager to store the results - MetricManager.setMetrics(suite.metrics); - - // Test Initialisation - assertDoesNotThrow(() -> storage = new CSVStorage(this.folder.toAbsolutePath().toString()), "Initialisation failed"); - assertTrue(Files.exists(this.suiteFolder), String.format("Result folder (%s) doesn't exist", this.suiteFolder)); - storage.storeResult(m); - - List expectedFiles; - try (Stream s = Files.list(suite.expectedFolder)) { - expectedFiles = s.toList(); - } - - for (Path expectedFile : expectedFiles) { - Path actualFile = suiteFolder.resolve(expectedFile.getFileName()); - assertTrue(Files.exists(actualFile), String.format("File (%s) doesn't exist", actualFile)); - assertDoesNotThrow(() -> compareCSVFiles(expectedFile, actualFile)); - } - } - } - - private void compareCSVFiles(Path expected, Path actual) throws IOException, CsvException { - try (CSVReader readerExpected = new CSVReader(new FileReader(expected.toFile())); - CSVReader readerActual = new CSVReader(new FileReader(actual.toFile()))) { - String[] headerExpected = readerExpected.readNext(); - String[] headerActual = readerActual.readNext(); - assertEquals(headerExpected.length, headerActual.length, String.format("Headers don't match. Actual: %s, Expected: %s", Arrays.toString(headerActual), Arrays.toString(headerExpected))); - for (int i = 0; i < headerExpected.length; i++) { - assertEquals(headerExpected[i], headerActual[i], String.format("Headers don't match. Actual: %s, Expected: %s", Arrays.toString(headerActual), Arrays.toString(headerExpected))); - } - - List expectedValues = new ArrayList<>(readerExpected.readAll()); - List actualValues = new ArrayList<>(readerActual.readAll()); - - for (String[] expectedLine : expectedValues) { - List sameLines = actualValues.stream().filter(x -> { - for (int i = 0; i < expectedLine.length; i++) { - if (!expectedLine[i].equals(x[i])) return false; - } - return true; - }).toList(); - - assertFalse(sameLines.isEmpty(), String.format("Line (%s) not found in actual file", Arrays.toString(expectedLine))); - actualValues.remove(sameLines.get(0)); - } - assertTrue(actualValues.isEmpty(), String.format("Actual file contains more lines than expected. Lines: %s", actualValues.stream().map(x -> "[" + String.join(", ", x) + "]").collect(Collectors.joining("\n")))); - } - } - - private record Suite(List taskResults, List metrics, Path expectedFolder) {} -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/NTFileStorageTest.java b/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/NTFileStorageTest.java deleted file mode 100644 index 79739c6a3..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/NTFileStorageTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks.storage.impl; - -import org.aksw.iguana.cc.tasks.stresstest.storage.impl.NTFileStorage; -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; -import org.apache.jena.rdf.model.*; -import org.apache.jena.vocabulary.RDFS; -import org.junit.Test; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * - * This will test the NTFileStorage in short. - * - * @author f.conrads - * - */ -public class NTFileStorageTest { - - - @Test - public void dataTest() throws IOException{ - Storage store = new NTFileStorage("results_test2.nt"); - - new File("results_test2.nt").delete(); - - Model m = ModelFactory.createDefaultModel(); - m.read(new FileReader("src/test/resources/nt/results_test1.nt"), null, "N-TRIPLE"); - - store.storeResult(m); - assertEqual("results_test2.nt","src/test/resources/nt/results_test1.nt", true); - new File("results_test2.nt").delete(); - - } - - /** - * Checks if two ntriple files are equal by loading them into a model and check if they have the same size - * and by removing the actual model from the expected, if the new size after removal equals 0 they are the same - * - * @param actualFile - * @param expectedFile - * @throws IOException - */ - public void assertEqual(String actualFile, String expectedFile, boolean ignoreDate) throws IOException{ - Model expected = ModelFactory.createDefaultModel(); - expected.read(new FileReader(expectedFile), null, "N-TRIPLE"); - Model actual = ModelFactory.createDefaultModel(); - actual.read(new FileReader(actualFile), null, "N-TRIPLE"); - assertEquals(expected.size(), actual.size()); - expected.remove(actual); - if(!ignoreDate){ - //Remove startDate as they are different, just check if actual contains a start date - Property startDate =ResourceFactory.createProperty(RDFS.getURI()+"startDate"); - assertTrue(actual.contains(null, startDate, (RDFNode)null)); - List stmts = expected.listStatements(null, startDate, (RDFNode)null).toList(); - assertEquals(1, stmts.size()); - expected.remove(stmts); - } - - assertEquals(0, expected.size()); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/RDFFileStorageTest.java b/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/RDFFileStorageTest.java deleted file mode 100644 index 757d0e6a0..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/RDFFileStorageTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks.storage.impl; - -import org.aksw.iguana.cc.tasks.stresstest.storage.impl.RDFFileStorage; -import org.aksw.iguana.cc.tasks.stresstest.storage.Storage; -import org.apache.jena.rdf.model.*; -import org.apache.jena.riot.RDFLanguages; -import org.apache.jena.vocabulary.RDFS; -import org.junit.Test; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * - * This will test the RDFFileStorage in short. - * - * @author l.conrads - * - */ -public class RDFFileStorageTest { - - - @Test - public void dataTest() throws IOException{ - Storage store = new RDFFileStorage("results_test2.ttl"); - - new File("results_test2.ttl").delete(); - - Model m = ModelFactory.createDefaultModel(); - m.read(new FileReader("src/test/resources/nt/results_test1.nt"), null, "N-TRIPLE"); - - store.storeResult(m); - - assertEqual("results_test2.ttl","src/test/resources/nt/results_test1.nt", true); - new File("results_test2.ttl").delete(); - - } - - - /** - * Checks if two ntriple files are equal by loading them into a model and check if they have the same size - * and by removing the actual model from the expected, if the new size after removal equals 0 they are the same - * - * @param actualFile - * @param expectedFile - * @throws IOException - */ - public void assertEqual(String actualFile, String expectedFile, boolean ignoreDate) throws IOException{ - Model expected = ModelFactory.createDefaultModel(); - expected.read(new FileReader(expectedFile), null, "N-TRIPLE"); - Model actual = ModelFactory.createDefaultModel(); - actual.read(new FileReader(actualFile), null, RDFLanguages.filenameToLang(actualFile).getName()); - assertEquals(expected.size(), actual.size()); - expected.remove(actual); - if(!ignoreDate){ - //Remove startDate as they are different, just check if actual contains a start date - Property startDate =ResourceFactory.createProperty(RDFS.getURI()+"startDate"); - assertTrue(actual.contains(null, startDate, (RDFNode)null)); - List stmts = expected.listStatements(null, startDate, (RDFNode)null).toList(); - assertEquals(1, stmts.size()); - expected.remove(stmts); - } - - assertEquals(0, expected.size()); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/TriplestoreStorageTest.java b/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/TriplestoreStorageTest.java deleted file mode 100644 index c1d883f87..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/storage/impl/TriplestoreStorageTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * - */ -package org.aksw.iguana.cc.tasks.storage.impl; - -import org.aksw.iguana.cc.tasks.stresstest.storage.impl.TriplestoreStorage; -import org.aksw.iguana.commons.constants.COMMON; -import org.aksw.iguana.cc.tasks.ServerMock; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.ResourceFactory; -import org.junit.After; -import org.junit.Test; -import org.simpleframework.http.core.ContainerServer; -import org.simpleframework.transport.connect.SocketConnection; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -import static org.junit.Assert.assertEquals; - -/** - * Will test if the TriplestoreStorage sends the correct INSERT command to a Mock Server - * - * @author f.conrads - * - */ -public class TriplestoreStorageTest { - - private static final int FAST_SERVER_PORT = 8023; - private ServerMock fastServerContainer; - private ContainerServer fastServer; - private SocketConnection fastConnection; - - private String metaExp = "INSERT DATA {\n" + - " .\n" + - " .\n" + - " .\n" + - " \"dbpedia\" .\n" + - " .\n" + - " \"virtuoso\" .\n" + - " .\n" + - " \"???\"^^ .\n" + - " .\n" + - " .\n" + - " .\n" + - " .\n" + - " .\n" + - "}"; - - private String dataExp = "INSERT DATA {\n"+ -" \"c\" .\n"+ -"}"; - - /** - * @throws IOException - */ - @After - public void close() throws IOException { - fastConnection.close(); - } - - /** - * @throws IOException - */ - @Test - public void dataTest() throws IOException{ - fastServerContainer = new ServerMock(); - fastServer = new ContainerServer(fastServerContainer); - fastConnection = new SocketConnection(fastServer); - SocketAddress address1 = new InetSocketAddress(FAST_SERVER_PORT); - fastConnection.connect(address1); - - String host = "http://localhost:8023"; - TriplestoreStorage store = new TriplestoreStorage(host, host); - - Model m = ModelFactory.createDefaultModel(); - m.add(ResourceFactory.createResource(COMMON.RES_BASE_URI+"a"), ResourceFactory.createProperty(COMMON.PROP_BASE_URI+"b") , "c"); - store.storeResult(m); - assertEquals(dataExp.trim(),fastServerContainer.getActualContent().trim()); - } - -} diff --git a/src/test/java/org/aksw/iguana/cc/tasks/stresstest/StresstestTest.java b/src/test/java/org/aksw/iguana/cc/tasks/stresstest/StresstestTest.java deleted file mode 100644 index 1ab15f22f..000000000 --- a/src/test/java/org/aksw/iguana/cc/tasks/stresstest/StresstestTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.aksw.iguana.cc.tasks.stresstest; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.tasks.MockupStorage; -import org.aksw.iguana.cc.tasks.stresstest.metrics.MetricManager; -import org.aksw.iguana.cc.tasks.stresstest.metrics.impl.EachExecutionStatistic; -import org.aksw.iguana.cc.worker.MockupWorker; -import org.aksw.iguana.cc.worker.Worker; -import org.aksw.iguana.cc.tasks.stresstest.storage.StorageManager; -import org.junit.Ignore; -import org.junit.Test; - -import java.time.Instant; -import java.util.*; - -import static org.junit.Assert.assertEquals; - -public class StresstestTest { - - // test correct # of worker creation, meta data and warmup - private final String[] queries = new String[]{"a", "b"}; - private final String[] queries2 = new String[]{"b", "c"}; - - private List> getWorkers(int threads, String[] queries) { - List> workers = new ArrayList<>(); - Map workerConfig = new HashMap<>(); - workerConfig.put("className", MockupWorker.class.getCanonicalName()); - workerConfig.put("stringQueries", queries); - workerConfig.put("threads", threads); - workers.add(workerConfig); - return workers; - } - - private ConnectionConfig getConnection() { - ConnectionConfig con = new ConnectionConfig(); - con.setName("test"); - con.setEndpoint("test/sparql"); - return con; - } - - private void init(){ - StorageManager storageManager = StorageManager.getInstance(); - MetricManager.setMetrics(List.of(new EachExecutionStatistic())); - MockupStorage storage = new MockupStorage(); - storageManager.addStorage(storage); - } - - @Test - public void checkStresstestNoQM() { - - Stresstest task = new Stresstest(getWorkers(2, this.queries), 10); - task.init(new String[]{"1", "1/1", "1/1/1"}, "test", getConnection()); - - init(); - - task.execute(); - - //2 queries in mix, 10 executions on 2 workers -> 40 queries - assertEquals(40, task.getExecutedQueries()); - } - - @Test - public void checkStresstestTL() { - - Stresstest task = new Stresstest(5000, getWorkers(2, this.queries)); - - task.init(new String[]{"1", "1/1", "1/1/1"}, "test", getConnection()); - - init(); - - Instant start = Instant.now(); - task.execute(); - Instant end = Instant.now(); - //give about 200milliseconds time for init and end stuff - assertEquals(5000.0, end.toEpochMilli() - start.toEpochMilli(), 300.0); - - } - - @Test - @Ignore("This test doesn't always pass. It expects a timing that is not guaranteed (or necessary).") - public void warmupTest() { - //check if not executing - Stresstest task = new Stresstest(5000, getWorkers(2, this.queries)); - - task.init(new String[]{"1", "1/1", "1/1/1"}, "test", getConnection()); - Instant start = Instant.now(); - assertEquals(0, task.warmup()); - Instant end = Instant.now(); - assertEquals(0.0, end.toEpochMilli() - start.toEpochMilli(), 5.0); - //check if executing - - Map warmup = new LinkedHashMap<>(); - warmup.put("workers", getWorkers(2, this.queries)); - warmup.put("timeLimit", 350); - - task = new Stresstest(5000, getWorkers(2, this.queries), warmup); - - task.init(new String[]{"1", "1/1", "1/1/1"}, "test", getConnection()); - start = Instant.now(); - long queriesExecuted = task.warmup(); - end = Instant.now(); - // might sadly be 400 or 500 as the warmup works in 100th steps, also overhead, as long as executed Queries are 6 its fine - assertEquals(350.0, end.toEpochMilli() - start.toEpochMilli(), 250.0); - //each worker could execute 3 query - assertEquals(6, queriesExecuted); - - } - - @Test - public void workerCreationTest() { - List> worker = getWorkers(2, this.queries); - worker.addAll(getWorkers(1, this.queries2)); - Stresstest task = new Stresstest(5000, worker); - - task.init(new String[]{"1", "1/1", "1/1/1"}, "test", getConnection()); - List workers = task.workers; - assertEquals(3, workers.size()); - int q1 = 0; - int q2 = 0; - // alittle bit hacky but should be sufficient - for (Worker w : workers) { - MockupWorker mockupWorker = (MockupWorker) w; - String[] queries = mockupWorker.getStringQueries(); - if (Arrays.hashCode(queries) == Arrays.hashCode(this.queries)) { - q1++; - } else if (Arrays.hashCode(queries) == Arrays.hashCode(this.queries2)) { - q2++; - } - } - assertEquals(2, q1); - assertEquals(1, q2); - - } -} diff --git a/src/test/java/org/aksw/iguana/cc/utils/CLIProcessManagerTest.java b/src/test/java/org/aksw/iguana/cc/utils/CLIProcessManagerTest.java deleted file mode 100644 index f16cf6319..000000000 --- a/src/test/java/org/aksw/iguana/cc/utils/CLIProcessManagerTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.*; - -@Ignore("CLI doesn't work right now") -public class CLIProcessManagerTest { - - @Test - public void execTest() throws InterruptedException { - //create process - Process p = CLIProcessManager.createProcess("echo \"abc\"; wait 1m"); - //destroy process - assertTrue(p.isAlive()); - CLIProcessManager.destroyProcess(p); - //give OS a little bit of time to destroy process - Thread.sleep(50); - assertFalse(p.isAlive()); - - } - - @Test - public void countLinesSuccessfulTest() throws IOException, InterruptedException { - //create - Process p = CLIProcessManager.createProcess("echo \"abc\"; wait 100; echo \"t\nt\nabc: test ended suffix\"; wait 1m;"); - //count Lines until "test ended" occured - Thread.sleep(100); - assertTrue(CLIProcessManager.isReaderReady(p)); - - assertEquals(3, CLIProcessManager.countLinesUntilStringOccurs(p, "test ended", "failed")); - //destroy - CLIProcessManager.destroyProcess(p); - //give OS a little bit of time to destroy process - Thread.sleep(50); - assertFalse(p.isAlive()); - - } - - @Test - public void countLinesFailTest() throws IOException, InterruptedException { - //create - Process p = CLIProcessManager.createProcess("echo \"abc\"; wait 100; echo \"abc: failed suffix\"; wait 1m;"); - Thread.sleep(100); - assertTrue(CLIProcessManager.isReaderReady(p)); - //count Lines until "test ended" occured - try{ - CLIProcessManager.countLinesUntilStringOccurs(p, "test ended", "failed"); - assertTrue("Test did not end in IOException", false); - }catch (IOException e){ - assertTrue(true); - } - //destroy - CLIProcessManager.destroyProcess(p); - //give OS a little bit of time to destroy process - Thread.sleep(50); - assertFalse(p.isAlive()); - - } - -} diff --git a/src/test/java/org/aksw/iguana/cc/utils/FileUtilsTest.java b/src/test/java/org/aksw/iguana/cc/utils/FileUtilsTest.java index 80a567c68..f213d73e0 100644 --- a/src/test/java/org/aksw/iguana/cc/utils/FileUtilsTest.java +++ b/src/test/java/org/aksw/iguana/cc/utils/FileUtilsTest.java @@ -1,9 +1,9 @@ package org.aksw.iguana.cc.utils; -import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import java.io.*; import java.nio.charset.StandardCharsets; @@ -15,157 +15,71 @@ import static java.nio.file.Files.createTempFile; import static org.apache.commons.io.FileUtils.writeStringToFile; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -@RunWith(Enclosed.class) public class FileUtilsTest { - - @RunWith(Parameterized.class) - public static class TestGetLineEnding { - private static class TestData { - public Path file; - public String expectedLineEnding; - - public TestData(String expectedLineEnding) { - this.expectedLineEnding = expectedLineEnding; - - - } - } - - public TestGetLineEnding(String expectedLineEnding) throws IOException { - this.data = new TestData(expectedLineEnding); - this.data.file = createTempFile("TestGetLineEnding", ".txt"); - this.data.file.toFile().deleteOnExit(); - writeStringToFile(this.data.file.toFile(), "a" + this.data.expectedLineEnding + "b" + this.data.expectedLineEnding, StandardCharsets.UTF_8); - } - - private final TestData data; - - @Parameterized.Parameters - public static Collection data() { - return List.of( - "\n", /* unix */ - "\r", /* old mac */ - "\r\n" /* windows */ - ); - } - - @Test - public void testGetLineEndings() throws IOException { - assertEquals(FileUtils.getLineEnding(this.data.file.toString()), this.data.expectedLineEnding); + public static Path createTestFileWithLines(List content, String lineEnding) throws IOException { + final var file = createTempFile("getHashTest", ".txt"); + for (String s : content) { + writeStringToFile(file.toFile(), s + lineEnding, StandardCharsets.UTF_8, true); } + file.toFile().deleteOnExit(); + return file; } - @RunWith(Parameterized.class) - public static class TestIndexStream { - - private final TestData data; - - public TestIndexStream(TestData data) { - this.data = data; - } - - public static class TestData { - /** - * String to be separated - */ - String string; - /** - * Separating sequence - */ - String separator; - - /** - * List of [offset, length] arrays - */ - List index; - - public TestData(String string, String separator, List index) { - this.string = string; - this.separator = separator; - this.index = index; - } - } - @Parameterized.Parameters - public static Collection data() { - - - return List.of( - new TestData("", "a", Arrays.asList(new long[]{0, 0})), - new TestData("a", "a", Arrays.asList(new long[]{0, 0}, new long[]{1, 0})), - new TestData("abc", "b", Arrays.asList(new long[]{0, 1}, new long[]{2, 1})), - new TestData("1\n2", "\n", Arrays.asList(new long[]{0, 1}, new long[]{2, 1})), - new TestData("1\t2", "\t", Arrays.asList(new long[]{0, 1}, new long[]{2, 1})), - new TestData("abcbd", "b", Arrays.asList(new long[]{0, 1}, new long[]{2, 1}, new long[]{4, 1})), - new TestData("aab", "ab", Arrays.asList(new long[]{0, 1}, new long[]{3, 0})), - new TestData("aaaabaabaa", "ab", Arrays.asList(new long[]{0, 3}, new long[]{5, 1}, new long[]{8, 2})), - new TestData("1\n\t\n2", "\n\t\n", Arrays.asList(new long[]{0, 1}, new long[]{4, 1})) - - ); - } - - @Test - public void testIndexingStrings() throws IOException { - //check if hash abs works - - List index = FileUtils.indexStream(data.separator, new ByteArrayInputStream(data.string.getBytes())); - - assertEquals(data.index.size(), index.size()); - for (int i = 0; i < index.size(); i++) { - assertArrayEquals(data.index.get(i), index.get(i)); - } - } + public static Path createTestFileWithContent(String content) throws IOException { + final var file = createTempFile("getHashTest", ".txt"); + writeStringToFile(file.toFile(), content, StandardCharsets.UTF_8, false); + file.toFile().deleteOnExit(); + return file; } - @RunWith(Parameterized.class) - public static class ParameterizedTest { - private final Path file; - - private final String content; - - public ParameterizedTest(String content) throws IOException { - this.file = createTempFile("getHashTest", ".txt"); - writeStringToFile(this.file.toFile(), content, StandardCharsets.UTF_8); - this.file.toFile().deleteOnExit(); - - this.content = content; - } + @ParameterizedTest + @ValueSource(strings = {"\n", "\r", "\r\n"}) + public void testGetLineEndings(String ending) throws IOException { + final var file = createTestFileWithLines(List.of("a", "b"), ending); + assertEquals(FileUtils.getLineEnding(file), ending); + } - @Parameterized.Parameters - public static Collection data() { + public record IndexTestData( + String content, // String to be separated + String separator, + List indices // List of [offset, length] arrays + ) {} + + public static Collection data() { + return List.of( + new IndexTestData("", "a", Arrays.asList(new long[]{0, 0})), + new IndexTestData("a", "a", Arrays.asList(new long[]{0, 0}, new long[]{1, 0})), + new IndexTestData("abc", "b", Arrays.asList(new long[]{0, 1}, new long[]{2, 1})), + new IndexTestData("1\n2", "\n", Arrays.asList(new long[]{0, 1}, new long[]{2, 1})), + new IndexTestData("1\t2", "\t", Arrays.asList(new long[]{0, 1}, new long[]{2, 1})), + new IndexTestData("abcbd", "b", Arrays.asList(new long[]{0, 1}, new long[]{2, 1}, new long[]{4, 1})), + new IndexTestData("aab", "ab", Arrays.asList(new long[]{0, 1}, new long[]{3, 0})), + new IndexTestData("aaaabaabaa", "ab", Arrays.asList(new long[]{0, 3}, new long[]{5, 1}, new long[]{8, 2})), + new IndexTestData("1\n\t\n2", "\n\t\n", Arrays.asList(new long[]{0, 1}, new long[]{4, 1})) + ); + } - return Arrays.asList( - UUID.randomUUID().toString(), - UUID.randomUUID().toString(), - UUID.randomUUID().toString(), - UUID.randomUUID().toString() - ); + @ParameterizedTest + @MethodSource("data") + public void testIndexingStrings(IndexTestData data) throws IOException { + List index = FileUtils.indexStream(data.separator, new ByteArrayInputStream(data.content.getBytes())); + assertEquals(data.indices.size(), index.size()); + for (int i = 0; i < index.size(); i++) { + assertArrayEquals(data.indices.get(i), index.get(i)); } + } - @Test - public void getHashTest(){ - //check if hash abs works + @Test + public void getHashTest() throws IOException { + for (int i = 0; i < 10; i++) { + String content = UUID.randomUUID().toString(); + final var file = createTestFileWithContent(content); final int expected = Math.abs(content.hashCode()); - final int actual = FileUtils.getHashcodeFromFileContent(this.file.toString()); + final int actual = FileUtils.getHashcodeFromFileContent(file); assertTrue(actual >= 0); assertEquals(expected, actual); } } - - public static class NonParameterizedTest { - @Test - public void readTest() throws IOException { - - Path file = createTempFile("readTest", ".txt"); - file.toFile().deleteOnExit(); - String expectedString = UUID.randomUUID() + "\n\t\r" + UUID.randomUUID() + "\n"; - writeStringToFile(file.toFile(), expectedString, StandardCharsets.UTF_8); - - //read whole content - String actualString = FileUtils.readFile(file.toString()); - - assertEquals(expectedString, actualString); - } - } } diff --git a/src/test/java/org/aksw/iguana/cc/utils/IndexedQueryReaderTest.java b/src/test/java/org/aksw/iguana/cc/utils/IndexedQueryReaderTest.java index 5166940d5..b279c8153 100644 --- a/src/test/java/org/aksw/iguana/cc/utils/IndexedQueryReaderTest.java +++ b/src/test/java/org/aksw/iguana/cc/utils/IndexedQueryReaderTest.java @@ -1,99 +1,169 @@ package org.aksw.iguana.cc.utils; -import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(Enclosed.class) public class IndexedQueryReaderTest { - @RunWith(Parameterized.class) - public static class ParameterizedTest { + private static Path tempDir; - IndexedQueryReader reader; + @BeforeAll + public static void createTestFolder() throws IOException { + tempDir = Files.createTempDirectory("iguana-indexed-query-reader-test"); + } - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList(new Object[][]{ - {"src/test/resources/readLineTestFile1.txt"}, - {"src/test/resources/readLineTestFile2.txt"}, - {"src/test/resources/readLineTestFile3.txt"} - }); - } + @AfterAll + public static void removeData() throws IOException { + org.apache.commons.io.FileUtils.deleteDirectory(tempDir.toFile()); + } - public ParameterizedTest(String path) throws IOException { - reader = IndexedQueryReader.make(path); + private record TestData ( + Path filepath, + String separator, + List expectedStrings + ) {} + + private static TestData createTestFile(String content, String separator, boolean emptyBegin, boolean leadingEmptyLine, int number, int spacing) throws IOException { + final var file = Files.createTempFile(tempDir, "line", "queries.txt"); + final var writer = new StringWriter(); + final var lines = new ArrayList(); + for (int i = (emptyBegin ? -1 : 0); i < (number * spacing) + 1; i++) { + if (i % spacing == 0) { + writer.append(content + i); + lines.add(content + i); + } + if (leadingEmptyLine || i != number * spacing) { + writer.append(separator); + } } + Files.writeString(file, writer.toString()); + return new TestData(file, separator, lines); + } - @Test - public void testIndexingWithLineEndings() throws IOException { - assertEquals("line 1", reader.readQuery(0)); - assertEquals("line 2", reader.readQuery(1)); - assertEquals("line 3", reader.readQuery(2)); - assertEquals("line 4", reader.readQuery(3)); + public static List indexWithLineEndingData() throws IOException { + final var out = new ArrayList(); + + final var numbers = List.of(1, 5, 10); + final var spacings = List.of(1, 2, 5, 10, 100, 1000000); + final var separators = List.of("\n", "\r\n", "\r"); + final var emptyBegins = List.of(true, false); + final var leadingEmptyLines = List.of(true, false); + + // cartesian product + for (var number : numbers) { + for (var spacing : spacings) { + for (var separator : separators) { + for (var emptyBegin : emptyBegins) { + for (var leadingEmptyLine : leadingEmptyLines) { + out.add(Arguments.of(createTestFile("line: ", separator, emptyBegin, leadingEmptyLine, number, spacing))); + } + } + } + } } - } - public static class NonParameterizedTest { - @Test - public void testIndexingWithBlankLines() throws IOException { - IndexedQueryReader reader = IndexedQueryReader.makeWithEmptyLines("src/test/resources/utils/indexingtestfile3.txt"); - String le = FileUtils.getLineEnding("src/test/resources/utils/indexingtestfile3.txt"); + return out; + } - assertEquals(" line 1" + le + "line 2", reader.readQuery(0)); - assertEquals("line 3", reader.readQuery(1)); - assertEquals("line 4" + le + "line 5", reader.readQuery(2)); + public static List indexWithBlankLinesData() throws IOException { + final var out = new ArrayList(); + + final var numbers = List.of(1, 5, 10, 100, 10000); + final var spacings = List.of(2); + final var separators = List.of("\n", "\r\n", "\r"); + final var emptyBegins = List.of(false); + final var leadingEmptyLines = List.of(false); + + // cartesian product + for (var number : numbers) { + for (var spacing : spacings) { + for (var separator : separators) { + for (var emptyBegin : emptyBegins) { + for (var leadingEmptyLine : leadingEmptyLines) { + out.add(Arguments.of(createTestFile(String.format("this is %s line: ", separator), separator, emptyBegin, leadingEmptyLine, number, spacing))); + out.add(Arguments.of(createTestFile("line: ", separator, emptyBegin, leadingEmptyLine, number, spacing))); + out.add(Arguments.of(createTestFile(String.format("make this %s three lines %s long: ", separator, separator), separator, emptyBegin, leadingEmptyLine, number, spacing))); + } + } + } + } } + + return out; } - @RunWith(Parameterized.class) - public static class TestCustomSeparator { - private static class TestData { - public String filepath; - public String separator; - public String[] expectedStrings; - - public TestData(String filepath, String separator, String[] expectedStrings) { - this.filepath = filepath; - this.separator = separator; - this.expectedStrings = expectedStrings; + public static List indexWithCustomSeparatorData() throws IOException { + final var out = new ArrayList(); + + final var numbers = List.of(1, 5, 10, 100, 10000); + final var spacings = List.of(1); + final var separators = List.of("\n", "\r\n", "\r", "\n+++\n", "\t\t\t", "test", "###$"); + final var emptyBegins = List.of(false); + final var leadingEmptyLines = List.of(false); + + // cartesian product + for (var number : numbers) { + for (var spacing : spacings) { + for (var separator : separators) { + for (var emptyBegin : emptyBegins) { + for (var leadingEmptyLine : leadingEmptyLines) { + out.add(Arguments.of(createTestFile("line: ", separator, emptyBegin, leadingEmptyLine, number, spacing))); + } + } + } } } - private TestData data; + final var file1 = Files.createTempFile(tempDir, "iguana", "queries.txt"); + final var file2 = Files.createTempFile(tempDir, "iguana", "queries.txt"); + Files.writeString(file1, "a####$b"); + Files.writeString(file2, "a21212111b"); + + out.add(Arguments.of(new TestData(file1, "###$", List.of("a#", "b")))); + out.add(Arguments.of(new TestData(file2, "211", List.of("a2121", "1b")))); + + return out; + } - public TestCustomSeparator(TestData data) { - this.data = data; + @ParameterizedTest + @MethodSource("indexWithLineEndingData") + public void testIndexingWithLineEndings(TestData data) throws IOException { + var reader = IndexedQueryReader.make(data.filepath); + for (int i = 0; i < data.expectedStrings.size(); i++) { + assertEquals(data.expectedStrings.get(i), reader.readQuery(i)); } + assertEquals(data.expectedStrings.size(), reader.size()); + } - @Parameterized.Parameters - public static Collection data() throws IOException { - // all the files should have the same line ending - String le = FileUtils.getLineEnding("src/test/resources/utils/indexingtestfile1.txt"); - return List.of( - new TestData("src/test/resources/utils/indexingtestfile1.txt", "#####" + le, new String[]{"line 1" + le, le + "line 2" + le}), - new TestData("src/test/resources/utils/indexingtestfile2.txt", "#####" + le, new String[]{"line 0" + le, "line 1" + le + "#####"}), - new TestData("src/test/resources/utils/indexingtestfile4.txt", "###$", new String[]{"a#", "b"}), - new TestData("src/test/resources/utils/indexingtestfile5.txt", "211", new String[]{"a21", "b"}) - ); + @ParameterizedTest + @MethodSource("indexWithBlankLinesData") + public void testIndexingWithBlankLines(TestData data) throws IOException { + IndexedQueryReader reader = IndexedQueryReader.makeWithEmptyLines(data.filepath); + for (int i = 0; i < data.expectedStrings.size(); i++) { + assertEquals(data.expectedStrings.get(i), reader.readQuery(i)); } + assertEquals(data.expectedStrings.size(), reader.size()); + } - @Test - public void testIndexingWithCustomSeparator() throws IOException { - IndexedQueryReader reader = IndexedQueryReader.makeWithStringSeparator(this.data.filepath, this.data.separator); - for (int i = 0; i < this.data.expectedStrings.length; i++) { - String read = reader.readQuery(i); - assertEquals(this.data.expectedStrings[i], read); - } - assertEquals(this.data.expectedStrings.length, reader.readQueries().size()); + @ParameterizedTest + @MethodSource("indexWithCustomSeparatorData") + public void testIndexingWithCustomSeparator(TestData data) throws IOException { + IndexedQueryReader reader = IndexedQueryReader.makeWithStringSeparator(data.filepath, data.separator); + for (int i = 0; i < data.expectedStrings.size(); i++) { + assertEquals(data.expectedStrings.get(i), reader.readQuery(i)); } + assertEquals(data.expectedStrings.size(), reader.size()); } } diff --git a/src/test/java/org/aksw/iguana/cc/utils/SPARQLQueryStatisticsTest.java b/src/test/java/org/aksw/iguana/cc/utils/SPARQLQueryStatisticsTest.java deleted file mode 100644 index be05c8757..000000000 --- a/src/test/java/org/aksw/iguana/cc/utils/SPARQLQueryStatisticsTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.apache.jena.query.Query; -import org.apache.jena.query.QueryFactory; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.ArrayList; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; - -@RunWith(Parameterized.class) -public class SPARQLQueryStatisticsTest { - - - private final String query; - private final double size; - private final int[] stats; - - @Parameterized.Parameters - public static Collection data(){ - Collection testData = new ArrayList(); - testData.add(new Object[]{"SELECT * {?s ?p ?o}", 1, new int[]{0, 0, 0, 0, 0, 0, 0, 0, 1}}); - testData.add(new Object[]{"SELECT * {?s ?p ?o. ?o ?p1 ?t}", 1, new int[]{0, 0, 0, 0, 0, 0, 0, 0, 2}}); - testData.add(new Object[]{"SELECT * {?s ?p ?o. ?o ?p1 ?t. FILTER (?t = \"test\")}", 1, new int[]{0, 1, 0, 0, 0, 0, 0, 0, 2}}); - //implicit groupBY as aggr - testData.add(new Object[]{"SELECT (COUNT(?s) AS ?co) {?s ?p ?o. ?o ?p1 ?t. FILTER (?t = \"test\")}", 1, new int[]{1, 1, 0, 0, 0, 1, 0, 0, 2}}); - testData.add(new Object[]{"SELECT * {?s ?p ?o. ?o ?p1 ?t. FILTER (?t = \"test\")} ORDER BY ?s", 1, new int[]{0, 1, 0, 0, 0, 0, 0, 1, 2}}); - testData.add(new Object[]{"SELECT ?s {?s ?p ?o. ?o ?p1 ?t. FILTER (?t = \"test\")} GROUP BY ?s", 1, new int[]{0, 1, 0, 0, 0, 1, 0, 0, 2}}); - testData.add(new Object[]{"SELECT ?o {{?s ?p ?o OPTIONAL {?o ?u ?s} } UNION { ?o ?p1 ?t}} OFFSET 10", 1, new int[]{0, 0, 1, 1, 0, 0, 1, 0, 3}}); - //implicit groupBY as aggr - testData.add(new Object[]{"SELECT * {?s ?p ?o} HAVING(COUNT(?s) > 1)", 1, new int[]{1, 0, 0, 0, 1, 1, 0, 0, 1}}); - - return testData; - } - - public SPARQLQueryStatisticsTest(String query, double size, int[] stats){ - this.query=query; - this.size=size; - this.stats=stats; - } - - @Test - public void checkCorrectStats(){ - SPARQLQueryStatistics qs = new SPARQLQueryStatistics(); - Query q = QueryFactory.create(this.query); - qs.getStatistics(q); - assertEquals(stats[0], qs.aggr); - assertEquals(stats[1], qs.filter); - assertEquals(stats[2], qs.optional); - assertEquals(stats[3], qs.union); - assertEquals(stats[4], qs.having); - assertEquals(stats[5], qs.groupBy); - assertEquals(stats[6], qs.offset); - assertEquals(size, qs.size, 0); - assertEquals(stats[7], qs.orderBy); - assertEquals(stats[8], qs.triples); - } - -} diff --git a/src/test/java/org/aksw/iguana/cc/utils/ServerMock.java b/src/test/java/org/aksw/iguana/cc/utils/ServerMock.java deleted file mode 100644 index b4485ae3c..000000000 --- a/src/test/java/org/aksw/iguana/cc/utils/ServerMock.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.aksw.iguana.cc.utils; - -import org.aksw.iguana.cc.lang.impl.SPARQLLanguageProcessor; -import org.apache.commons.io.FileUtils; -import org.simpleframework.http.Request; -import org.simpleframework.http.Response; -import org.simpleframework.http.Status; -import org.simpleframework.http.core.Container; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; - -/** - * Server Mock representing a TS - * - * @author f.conrads - * - */ -public class ServerMock implements Container { - - private static final Logger LOGGER = LoggerFactory.getLogger(ServerMock.class); - private String actualContent; - - - @Override - public void handle(Request request, Response resp) { - String content=null; - try { - content = request.getContent(); - } catch (IOException e) { - LOGGER.error("Got exception.", e); - } - resp.setCode(Status.OK.code); - resp.setContentType(SPARQLLanguageProcessor.QUERY_RESULT_TYPE_JSON); - try { - //write answer - String resultStr = FileUtils.readFileToString(new File("src/test/resources/sparql-json-response.json"), "UTF-8"); - resp.getOutputStream().write(resultStr.getBytes()); - resp.getOutputStream().close(); - } catch (IOException e) { - LOGGER.error("Could not close Response Output Stream"); - } - } - - -} diff --git a/src/test/java/org/aksw/iguana/cc/worker/HTTPWorkerTest.java b/src/test/java/org/aksw/iguana/cc/worker/HTTPWorkerTest.java deleted file mode 100644 index a875295a1..000000000 --- a/src/test/java/org/aksw/iguana/cc/worker/HTTPWorkerTest.java +++ /dev/null @@ -1,217 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.lang.impl.SPARQLLanguageProcessor; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.utils.FileUtils; -import org.aksw.iguana.cc.worker.impl.HttpGetWorker; -import org.aksw.iguana.cc.worker.impl.HttpPostWorker; -import org.aksw.iguana.cc.worker.impl.HttpWorker; -import org.junit.*; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.simpleframework.http.core.ContainerServer; -import org.simpleframework.transport.connect.SocketConnection; - -import java.io.File; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static org.junit.Assert.*; - -@RunWith(Parameterized.class) -public class HTTPWorkerTest { - - private static final int FAST_SERVER_PORT = 8025; - private static ContainerServer fastServer; - private static SocketConnection fastConnection; - private final String service; - private final Boolean isPost; - private final HashMap queries; - - private final String queriesFile = "src/test/resources/workers/single-query.txt"; - private final String responseType; - private final String parameter; - private final String query; - private final String queryID; - private final boolean isFail; - private String outputDir; - private final Integer fixedLatency; - private final Integer gaussianLatency; - - public HTTPWorkerTest(String query, String queryID, String responseType, String parameter, Integer fixedLatency, Integer gaussianLatency, Boolean isFail, Boolean isPost) { - this.query = query; - this.queryID = queryID; - this.responseType = responseType; - this.parameter = parameter; - this.isFail = isFail; - this.isPost = isPost; - this.fixedLatency = fixedLatency; - this.gaussianLatency = gaussianLatency; - this.service = "http://localhost:8025"; - - this.queries = new HashMap<>(); - this.queries.put("location", this.queriesFile); - - //warmup - getWorker("1").executeQuery("test", "test"); - } - - @Parameterized.Parameters - public static Collection data() { - Collection testData = new ArrayList<>(); - //get tests - testData.add(new Object[]{"Random Text", "doc1", "text/plain", "text", 100, 50, false, false}); - testData.add(new Object[]{UUID.randomUUID().toString(), UUID.randomUUID().toString(), "text/plain", "text", 100, 50, false, false}); - - testData.add(new Object[]{"Random Text", "doc1", "text/plain", "test", 100, 50, true, false}); - testData.add(new Object[]{"Random Text", "doc1", null, "text", 100, 50, false, false}); - - //post tests - testData.add(new Object[]{"Random Text", "doc1", "text/plain", "text", 100, 50, false, true}); - testData.add(new Object[]{UUID.randomUUID().toString(), UUID.randomUUID().toString(), "text/plain", "text", 100, 50, false, true}); - - testData.add(new Object[]{"Random Text", "doc1", "text/plain", "test", 100, 50, true, true}); - testData.add(new Object[]{"Random Text", "doc1", "text/plain", null, 100, 50, true, true}); - testData.add(new Object[]{"Random Text", "doc1", null, "text", 100, 50, false, true}); - - return testData; - } - - @BeforeClass - public static void startServer() throws IOException { - fastServer = new ContainerServer(new WorkerServerMock()); - fastConnection = new SocketConnection(fastServer); - SocketAddress address1 = new InetSocketAddress(FAST_SERVER_PORT); - fastConnection.connect(address1); - - } - - @AfterClass - public static void stopServer() throws IOException { - fastConnection.close(); - fastServer.stop(); - } - - @Before - public void setOutputDir() { - this.outputDir = UUID.randomUUID().toString(); - } - - @After - public void deleteFolder() throws IOException { - org.apache.commons.io.FileUtils.deleteDirectory(new File(this.outputDir)); - } - - @Test - public void testExecution() throws InterruptedException { - // check if correct param name was set - String taskID = "123/1/1/"; - - HttpWorker getWorker = getWorker(taskID); - - getWorker.executeQuery(this.query, this.queryID); - //as the result processing is in the background we have to wait for it. - Thread.sleep(1000); - Collection results = getWorker.popQueryResults(); - assertEquals(1, results.size()); - QueryExecutionStats p = results.iterator().next(); - - assertEquals(taskID, getWorker.taskID); - - assertEquals(this.queryID, p.queryID()); - if (isPost) { - assertEquals(200.0, p.executionTime(), 20.0); - } else { - assertEquals(100.0, p.executionTime(), 20.0); - } - if (isFail) { - assertEquals(-2L, p.responseCode()); - assertEquals(0L, p.resultSize()); - } else { - assertEquals(1L, p.responseCode()); - if (this.responseType != null && this.responseType.equals("text/plain")) { - assertEquals(4L, p.resultSize()); - } - if (this.responseType == null || this.responseType.equals(SPARQLLanguageProcessor.QUERY_RESULT_TYPE_JSON)) { - assertEquals(2L, p.resultSize()); - } - } - assertEquals(1, getWorker.getExecutedQueries()); - } - - private HttpWorker getWorker(String taskID) { - return getWorker(taskID, null, null); - } - - private HttpWorker getWorker(String taskID, Integer latencyFixed, Integer gaussianFixed) { - if (this.isPost) { - return new HttpPostWorker(taskID, 1, getConnection(), this.queries, null, null, latencyFixed, gaussianFixed, this.parameter, this.responseType, "application/json"); - } - return new HttpGetWorker(taskID, 1, getConnection(), this.queries, null, null, latencyFixed, gaussianFixed, this.parameter, this.responseType); - - } - - private ConnectionConfig getConnection() { - ConnectionConfig con = new ConnectionConfig(); - con.setName("test"); - con.setPassword("test"); - con.setUser("abc"); - con.setEndpoint(this.service); - con.setUpdateEndpoint(this.service); - return con; - } - - @Test - public void testWait() throws InterruptedException { - String taskID = "123/1/1/"; - HttpWorker getWorker = getWorker(taskID, this.fixedLatency, this.gaussianLatency); - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(getWorker); - long waitMS = 850; - Thread.sleep(waitMS); - getWorker.stopSending(); - executorService.shutdownNow(); - //get expected delay - int expectedDelay = 100 + this.fixedLatency + this.gaussianLatency; - if (this.isPost) { - expectedDelay += 100; - } - double expectedQueries = waitMS * 1.0 / expectedDelay; - double deltaUp = waitMS * 1.0 / (expectedDelay + this.gaussianLatency); - double deltaDown = waitMS * 1.0 / (expectedDelay - this.gaussianLatency); - double delta = Math.ceil((deltaDown - deltaUp) / 2); - assertEquals(expectedQueries, 1.0 * getWorker.getExecutedQueries(), delta); - } - - @Test - public void testWorkflow() throws InterruptedException { - // check as long as not endsignal - String taskID = "123/1/1/"; - int queryHash = FileUtils.getHashcodeFromFileContent(this.queriesFile); - - HttpWorker getWorker = getWorker(taskID); - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(getWorker); - Thread.sleep(450); - getWorker.stopSending(); - executorService.shutdownNow(); - // check correct executedQueries - long expectedSize = 4; - if (this.isPost) { - expectedSize = 2; - } - assertEquals(expectedSize, getWorker.getExecutedQueries()); - // check pop query results - Collection results = getWorker.popQueryResults(); - assertEquals(expectedSize, results.size()); - for (long i = 1; i < expectedSize; i++) { - assertTrue(getWorker.hasExecutedNoOfQueryMixes(i)); - } - assertFalse(getWorker.hasExecutedNoOfQueryMixes(expectedSize + 1)); - } -} diff --git a/src/test/java/org/aksw/iguana/cc/worker/MockupWorker.java b/src/test/java/org/aksw/iguana/cc/worker/MockupWorker.java deleted file mode 100644 index 2242f7bc0..000000000 --- a/src/test/java/org/aksw/iguana/cc/worker/MockupWorker.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.commons.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - -public class MockupWorker extends AbstractWorker { - - private int counter = 0; - private final String[] queries; - - public MockupWorker(String[] stringQueries, Integer workerID, @Nullable Integer timeLimit, ConnectionConfig connection, String taskID) { - super(taskID, workerID, connection, getQueryConfig(), 0, timeLimit, 0, 0); - this.queries = stringQueries; - } - - public String[] getStringQueries() { - return queries; - } - - private static Map getQueryConfig() { - Map queryConfig = new HashMap<>(); - queryConfig.put("location", "src/test/resources/mockupq.txt"); - return queryConfig; - } - - @Override - public void executeQuery(String query, String queryID) { - long execTime = this.workerID * 10 + 100; - long responseCode; - long resultSize; - try { - Thread.sleep(execTime); - responseCode = 200; - resultSize = this.workerID * 100 + 100; - } catch (InterruptedException e) { - e.printStackTrace(); - responseCode = 400; - resultSize = 0; - } - super.addResults(new QueryExecutionStats(queryID, responseCode, execTime, resultSize)); - } - - @Override - public void getNextQuery(StringBuilder queryStr, StringBuilder queryID) { - if (this.counter >= this.queries.length) { - this.counter = 0; - } - queryStr.append(this.queries[this.counter]); - queryID.append("src/test/resources/mockupq.txt:").append(this.counter); - this.counter++; - } -} diff --git a/src/test/java/org/aksw/iguana/cc/worker/UPDATEWorkerTest.java b/src/test/java/org/aksw/iguana/cc/worker/UPDATEWorkerTest.java deleted file mode 100644 index 3b1310492..000000000 --- a/src/test/java/org/aksw/iguana/cc/worker/UPDATEWorkerTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.worker.impl.UPDATEWorker; -import org.aksw.iguana.cc.worker.impl.update.UpdateTimer; -import org.aksw.iguana.commons.time.TimeUtils; -import org.apache.commons.io.FileUtils; -import org.junit.*; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.simpleframework.http.core.ContainerServer; -import org.simpleframework.transport.connect.SocketConnection; - -import java.io.File; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.time.Instant; -import java.util.*; - -import static org.junit.Assert.assertEquals; - - -@RunWith(Parameterized.class) -public class UPDATEWorkerTest { - - private static final int FAST_SERVER_PORT = 8025; - private static WorkerServerMock fastServerContainer; - private static ContainerServer fastServer; - private static SocketConnection fastConnection; - private final String service; - private final String timerStrategy; - private final Map queriesFile; - private final int expectedExec; - private String outputDir; - - public UPDATEWorkerTest(String timerStrategy, Map queriesFile, int expectedExec) { - this.service = "http://localhost:8025/test"; - this.timerStrategy = timerStrategy; - this.queriesFile = queriesFile; - this.expectedExec = expectedExec; - //warmup - Map warmupQueries = new HashMap<>(); - warmupQueries.put("location", "src/test/resources/workers/single-query.txt"); - UPDATEWorker worker = new UPDATEWorker("", 1, getConnection(), warmupQueries, null, null, null, null, null); - worker.executeQuery("INSERT DATA {", "1"); - fastServerContainer.getTimes().clear(); - fastServerContainer.getEncodedAuth().clear(); - } - - @Parameterized.Parameters - public static Collection data() { - Collection testData = new ArrayList<>(); - - Map queries0 = new HashMap<>(); - queries0.put("location", "src/test/resources/workers/updates"); - queries0.put("format", "folder"); - testData.add(new Object[]{"none", queries0, 4}); - - Map queries1 = new HashMap<>(); - queries1.put("location", "src/test/resources/workers/updates"); - queries1.put("format", "folder"); - testData.add(new Object[]{"fixed", queries1, 4}); - - Map queries2 = new HashMap<>(); - queries2.put("location", "src/test/resources/workers/updates"); - queries2.put("format", "folder"); - testData.add(new Object[]{"distributed", queries2, 4}); - - Map queries3 = new HashMap<>(); - queries3.put("location", "src/test/resources/workers/updates.txt"); - testData.add(new Object[]{"none", queries3, 3}); - - Map queries4 = new HashMap<>(); - queries4.put("location", "src/test/resources/workers/updates.txt"); - testData.add(new Object[]{"fixed", queries4, 3}); - - Map queries5 = new HashMap<>(); - queries5.put("location", "src/test/resources/workers/updates.txt"); - testData.add(new Object[]{"distributed", queries5, 3}); - return testData; - } - - @BeforeClass - public static void startServer() throws IOException { - fastServerContainer = new WorkerServerMock(true); - fastServer = new ContainerServer(fastServerContainer); - fastConnection = new SocketConnection(fastServer); - SocketAddress address1 = new InetSocketAddress(FAST_SERVER_PORT); - fastConnection.connect(address1); - - } - - @AfterClass - public static void stopServer() throws IOException { - fastConnection.close(); - fastServer.stop(); - } - - @Before - public void createDir() { - this.outputDir = UUID.randomUUID().toString(); - } - - @After - public void cleanup() throws IOException { - FileUtils.deleteDirectory(new File(this.outputDir)); - fastServerContainer.getTimes().clear(); - fastServerContainer.getEncodedAuth().clear(); - } - - // creds correct - // stop sending after iteration - // correct timer strategy - // correct waiting in sum - @Test - public void testWorkflow() throws InterruptedException { - String taskID = "124/1/1"; - int timeLimit = 2000; - ConnectionConfig con = getConnection(); - UPDATEWorker worker = new UPDATEWorker(taskID, 1, con, this.queriesFile, timeLimit, null, null, null, this.timerStrategy); - worker.run(); - Instant now = worker.startTime; - - Thread.sleep(2000); - assertEquals(this.expectedExec, worker.getExecutedQueries()); - - Set creds = fastServerContainer.getEncodedAuth(); - assertEquals(1, creds.size()); - assertEquals(con.getUser() + ":" + con.getPassword(), creds.iterator().next()); - List requestTimes = fastServerContainer.getTimes(); - long noOfQueries = worker.getNoOfQueries(); - Double fixedValue = timeLimit / noOfQueries * 1.0; - Instant pastInstant = requestTimes.get(0); - - long remainingQueries = noOfQueries - 1; - long remainingTime = timeLimit - Double.valueOf(TimeUtils.durationInMilliseconds(now, pastInstant)).longValue(); - for (int i = 1; i < requestTimes.size(); i++) { - //every exec needs about 200ms - Instant currentInstant = requestTimes.get(i); - double timeInMS = TimeUtils.durationInMilliseconds(pastInstant, currentInstant); - double expected = getQueryWaitTime(this.timerStrategy, fixedValue, remainingQueries, remainingTime); - assertEquals("Run " + i, expected, timeInMS, 200.0); - remainingTime = timeLimit - (100 + Double.valueOf(TimeUtils.durationInMilliseconds(now, currentInstant)).longValue()); - remainingQueries--; - pastInstant = currentInstant; - } - } - - private double getQueryWaitTime(String timerStrategy, Double fixedValue, long remainingQueries, long remainingTime) { - UpdateTimer.Strategy timer = UpdateTimer.Strategy.valueOf(timerStrategy.toUpperCase()); - switch (timer) { - case FIXED: - return fixedValue + 100.0; - case DISTRIBUTED: - return remainingTime * 1.0 / remainingQueries; - case NONE: - return 100.0; - - } - return 0; - } - - - private ConnectionConfig getConnection() { - ConnectionConfig con = new ConnectionConfig(); - con.setName("test"); - con.setEndpoint(this.service); - - con.setUpdateEndpoint(this.service); - con.setUser("testuser"); - con.setPassword("testpwd"); - return con; - } -} diff --git a/src/test/java/org/aksw/iguana/cc/worker/WorkerServerMock.java b/src/test/java/org/aksw/iguana/cc/worker/WorkerServerMock.java deleted file mode 100644 index 948a525cb..000000000 --- a/src/test/java/org/aksw/iguana/cc/worker/WorkerServerMock.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.aksw.iguana.cc.worker; - -import org.aksw.iguana.cc.lang.impl.SPARQLLanguageProcessor; -import org.apache.commons.io.FileUtils; -import org.simpleframework.http.Request; -import org.simpleframework.http.Response; -import org.simpleframework.http.Status; -import org.simpleframework.http.core.Container; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.time.Instant; -import java.util.*; - -/** - * Server Mock - * - * @author f.conrads - * - */ -public class WorkerServerMock implements Container { - - private static final Logger LOGGER = LoggerFactory.getLogger(WorkerServerMock.class); - private final Boolean ignore; - - private List requestTimes = new ArrayList(); - private Set encodedAuth = new HashSet(); - - public WorkerServerMock() { - this(false); - } - - public WorkerServerMock(Boolean ignore){ - super(); - this.ignore =ignore; - } - - @Override - public void handle(Request request, Response resp) { - String content=null; - requestTimes.add(Instant.now()); - if(ignore){ - String authValue = request.getValue("Authorization").replace("Basic ", ""); - this.encodedAuth.add(new String(Base64.getDecoder().decode(authValue))); - waitForMS(95); - try { - content = request.getContent(); - }catch (IOException e){ - LOGGER.error("", e); - } - } - else if(request.getMethod().equals("GET")) { - waitForMS(95); - content=request.getParameter("text"); - } - else if(request.getMethod().equals("POST")){ - waitForMS(195); - try { - String postContent = request.getContent(); - if(postContent.startsWith("{ \"text\":")){ - content=postContent; - } - } catch (IOException e) { - LOGGER.error("", e); - } - } - - if(content!=null){ - handleOK(resp, request.getValue("accept")); - } - else{ - handleFail(resp, request.getValue("accept")); - } - - } - - private void waitForMS(long ms){ - try { - Thread.sleep(ms); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - public void handleFail(Response resp, String acceptType){ - resp.setCode(Status.BAD_REQUEST.code); - String cType = acceptType; - if(acceptType==null){ - cType = SPARQLLanguageProcessor.QUERY_RESULT_TYPE_JSON; - } - resp.setContentType(cType); - try { - //write answer - resp.getOutputStream().write("".getBytes()); - resp.getOutputStream().close(); - } catch (IOException e) { - LOGGER.error("Could not close Response Output Stream"); - } - } - - public void handleUnAuthorized(Response resp){ - resp.setCode(Status.UNAUTHORIZED.code); - try { - //write answer - resp.getOutputStream().write("".getBytes()); - resp.getOutputStream().close(); - } catch (IOException e) { - LOGGER.error("Could not close Response Output Stream"); - } - } - - public void handleOK(Response resp, String acceptType){ - resp.setCode(Status.OK.code); - String cType = acceptType; - if(acceptType==null){ - cType = SPARQLLanguageProcessor.QUERY_RESULT_TYPE_JSON; - } - resp.setContentType(cType); - - try { - //write answer - String resultStr=""; - if(cType.equals("text/plain")){ - resultStr="a\nb\nc\nd"; - } - else if(cType.equals(SPARQLLanguageProcessor.QUERY_RESULT_TYPE_JSON)) { - resultStr = FileUtils.readFileToString(new File("src/test/resources/sparql-json-response.json"), "UTF-8"); - } - resp.getOutputStream().write(resultStr.getBytes()); - resp.getOutputStream().close(); - } catch (IOException e) { - LOGGER.error("Could not close Response Output Stream"); - } - } - - public List getTimes(){ - return this.requestTimes; - } - - public Set getEncodedAuth() { - return encodedAuth; - } -} diff --git a/src/test/java/org/aksw/iguana/cc/worker/impl/CLIWorkersTests.java b/src/test/java/org/aksw/iguana/cc/worker/impl/CLIWorkersTests.java deleted file mode 100644 index 9aca35f85..000000000 --- a/src/test/java/org/aksw/iguana/cc/worker/impl/CLIWorkersTests.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.aksw.iguana.cc.model.QueryExecutionStats; -import org.aksw.iguana.cc.utils.FileUtils; -import org.aksw.iguana.commons.constants.COMMON; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@Ignore("CLI workers don't work right now") -public class CLIWorkersTests { - - private File f; - - @Before - public void createFile() { - String file = UUID.randomUUID().toString(); - this.f = new File(file); - } - - @After - public void deleteFile() { - this.f.delete(); - } - - @Test - public void checkMultipleProcesses() { - ConnectionConfig con = new ConnectionConfig(); - con.setEndpoint("src/test/resources/cli/echoinput.sh " + f.getAbsolutePath()); - MultipleCLIInputWorker worker = new MultipleCLIInputWorker("123/1/1", 1, con, getQueryConfig(), null, null, null, null, "init finished", "rows", "query fail", 2); - assertEquals(2, worker.processList.size()); - for (Process p : worker.processList) { - assertTrue(p.isAlive()); - } - //should run normally - assertEquals(0, worker.currentProcessId); - worker.executeQuery("test", "1"); - assertEquals(0, worker.currentProcessId); - worker.executeQuery("quit", "2"); - worker.executeQuery("test", "1"); - assertEquals(1, worker.currentProcessId); - assertEquals(2, worker.processList.size()); - - for (Process p : worker.processList) { - assertTrue(p.isAlive()); - } - worker.executeQuery("quit", "2"); - worker.executeQuery("test", "1"); - assertEquals(0, worker.currentProcessId); - } - - @Test - public void checkFileInput() throws IOException { - //check if file is created and used - ConnectionConfig con = new ConnectionConfig(); - String dir = UUID.randomUUID().toString(); - con.setEndpoint("src/test/resources/cli/echoinput.sh " + f.getAbsolutePath()); - CLIInputFileWorker worker = new CLIInputFileWorker("123/1/1", 1, con, getQueryConfig(), null, null, null, null, "init finished", "rows", "query fail", 1, dir); - worker.executeQuery("test", "1"); - assertEquals("test", FileUtils.readFile(dir + File.separator + "tmpquery.sparql")); - worker.executeQuery("SELECT whatever", "1"); - assertEquals("SELECT whatever", FileUtils.readFile(dir + File.separator + "tmpquery.sparql")); - assertEquals("tmpquery.sparql\ntmpquery.sparql\n", FileUtils.readFile(f.getAbsolutePath())); - - org.apache.commons.io.FileUtils.deleteDirectory(new File(dir)); - worker.stopSending(); - - } - - @Test - public void checkInput() throws IOException { - // check if connection stays - ConnectionConfig con = new ConnectionConfig(); - - con.setEndpoint("src/test/resources/cli/echoinput.sh " + f.getAbsolutePath()); - CLIInputWorker worker = new CLIInputWorker("123/1/1", 1, con, getQueryConfig(), null, null, null, null, "init finished", "rows", "query fail"); - worker.executeQuery("test", "1"); - worker.executeQuery("SELECT whatever", "1"); - assertEquals("test\nSELECT whatever\n", FileUtils.readFile(f.getAbsolutePath())); - Collection succeededResults = worker.popQueryResults(); - assertEquals(2, succeededResults.size()); - QueryExecutionStats succ = succeededResults.iterator().next(); - assertEquals(COMMON.QUERY_SUCCESS, succ.responseCode()); - assertEquals(3L, succ.responseCode()); - succ = succeededResults.iterator().next(); - assertEquals(COMMON.QUERY_SUCCESS, succ.responseCode()); - assertEquals(3L, succ.responseCode()); - - // check fail - worker.executeQuery("fail", "2"); - assertEquals("test\nSELECT whatever\nfail\n", FileUtils.readFile(f.getAbsolutePath())); - Collection failedResults = worker.popQueryResults(); - assertEquals(1, failedResults.size()); - QueryExecutionStats fail = failedResults.iterator().next(); - assertEquals(COMMON.QUERY_UNKNOWN_EXCEPTION, fail.responseCode()); - assertEquals(0L, fail.resultSize()); - worker.stopSending(); - - - } - - @Test - public void checkPrefix() throws IOException { - // check if connection stays - ConnectionConfig con = new ConnectionConfig(); - - con.setEndpoint("src/test/resources/cli/echoinput.sh " + f.getAbsolutePath()); - CLIInputPrefixWorker worker = new CLIInputPrefixWorker("123/1/1", 1, con, getQueryConfig(), null, null, null, null, "init finished", "rows", "query fail", 1, "prefix", "suffix"); - worker.executeQuery("test", "1"); - worker.executeQuery("SELECT whatever", "1"); - assertEquals("prefix test suffix\nprefix SELECT whatever suffix\n", FileUtils.readFile(f.getAbsolutePath())); - Collection succeededResults = worker.popQueryResults(); - assertEquals(2, succeededResults.size()); - QueryExecutionStats succ = succeededResults.iterator().next(); - assertEquals(COMMON.QUERY_SUCCESS, succ.responseCode()); - assertEquals(3L, succ.resultSize()); - succ = succeededResults.iterator().next(); - assertEquals(COMMON.QUERY_SUCCESS, succ.responseCode()); - assertEquals(3L, succ.resultSize()); - - // check fail - worker.executeQuery("fail", "2"); - assertEquals("prefix test suffix\nprefix SELECT whatever suffix\nprefix fail suffix\n", FileUtils.readFile(f.getAbsolutePath())); - Collection failedResults = worker.popQueryResults(); - assertEquals(1, failedResults.size()); - QueryExecutionStats fail = failedResults.iterator().next(); - assertEquals(COMMON.QUERY_UNKNOWN_EXCEPTION, fail.responseCode()); - assertEquals(0L, fail.resultSize()); - worker.stopSending(); - } - - @Test - public void checkCLI() throws IOException { - //check if simple cli works - // public CLIWorker(String taskID, Connection connection, String queriesFile, @Nullable Integer timeOut, @Nullable Integer timeLimit, @Nullable Integer fixedLatency, @Nullable Integer gaussianLatency, Integer workerID) { - ConnectionConfig con = new ConnectionConfig(); - con.setUser("user1"); - con.setPassword("pwd"); - - con.setEndpoint("/bin/echo \"$QUERY$ $USER$:$PASSWORD$ $ENCODEDQUERY$\" > " + f.getAbsolutePath()); - CLIWorker worker = new CLIWorker("123/1/1", 1, con, getQueryConfig(), null, null, null, null); - worker.executeQuery("test ()", "1"); - String content = FileUtils.readFile(f.getAbsolutePath()); - assertEquals("test () user1:pwd test+%28%29\n", content); - - con = new ConnectionConfig(); - con.setEndpoint("/bin/echo \"$QUERY$ $USER$:$PASSWORD$ $ENCODEDQUERY$\" > " + f.getAbsolutePath() + " | /bin/printf \"HeaderDoesNotCount\na\na\""); - worker = new CLIWorker("123/1/1", 1, con, getQueryConfig(), null, null, null, null); - worker.executeQuery("test ()", "1"); - content = FileUtils.readFile(f.getAbsolutePath()); - assertEquals("test () : test+%28%29\n", content); - Collection results = worker.popQueryResults(); - assertEquals(1, results.size()); - QueryExecutionStats p = results.iterator().next(); - assertEquals(2L, p.resultSize()); - } - - private Map getQueryConfig() { - Map config = new HashMap<>(); - config.put("location", "src/test/resources/updates/empty.nt"); - return config; - } -} diff --git a/src/test/java/org/aksw/iguana/cc/worker/impl/HttpPostWorkerTest.java b/src/test/java/org/aksw/iguana/cc/worker/impl/HttpPostWorkerTest.java deleted file mode 100644 index 631319a22..000000000 --- a/src/test/java/org/aksw/iguana/cc/worker/impl/HttpPostWorkerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.aksw.iguana.cc.worker.impl; - -import org.aksw.iguana.cc.config.elements.ConnectionConfig; -import org.apache.http.client.methods.HttpPost; -import org.junit.Test; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; - -public class HttpPostWorkerTest { - - private static Map getDefaultQueryConfig() { - Map queries = new HashMap<>(); - queries.put("location", "src/test/resources/workers/single-query.txt"); - return queries; - } - - @Test - public void buildRequest() throws IOException { - String query = "DELETE DATA { \"äöüÄÖÜß\" . }"; - - HttpPostWorker postWorker = new HttpPostWorker(null, 0, getConnection(), getDefaultQueryConfig(), null, null, null, null, null, null, "application/sparql"); - postWorker.buildRequest(query, null); - - HttpPost request = ((HttpPost) postWorker.request); - - assertEquals("Content-Type: text/plain; charset=UTF-8", request.getEntity().getContentType().toString()); - - String content = new BufferedReader(new InputStreamReader(request.getEntity().getContent(), StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); - assertEquals(query, content); - } - - private ConnectionConfig getConnection() { - String service = "http://localhost:3030"; - - ConnectionConfig con = new ConnectionConfig(); - con.setName("test"); - con.setPassword("test"); - con.setUser("abc"); - con.setEndpoint(service); - con.setUpdateEndpoint(service); - return con; - } -} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/cc/worker/impl/RequestFactoryTest.java b/src/test/java/org/aksw/iguana/cc/worker/impl/RequestFactoryTest.java new file mode 100644 index 000000000..ef1c09d9f --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/worker/impl/RequestFactoryTest.java @@ -0,0 +1,110 @@ +package org.aksw.iguana.cc.worker.impl; + +import org.aksw.iguana.cc.mockup.MockupConnection; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.net.http.HttpResponse; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.Flow; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestFactoryTest { + static final class StringSubscriber implements Flow.Subscriber { + final HttpResponse.BodySubscriber wrapped; + StringSubscriber(HttpResponse.BodySubscriber wrapped) { + this.wrapped = wrapped; + } + @Override + public void onSubscribe(Flow.Subscription subscription) { + wrapped.onSubscribe(subscription); + } + @Override + public void onNext(ByteBuffer item) { wrapped.onNext(List.of(item)); } + @Override + public void onError(Throwable throwable) { wrapped.onError(throwable); } + @Override + public void onComplete() { wrapped.onComplete(); } + } + + + @ParameterizedTest + @EnumSource(SPARQLProtocolWorker.RequestFactory.RequestType.class) + public void test(SPARQLProtocolWorker.RequestFactory.RequestType type) throws URISyntaxException, IOException { + final var content = "SELECT * WHERE { ?s ?p ?o }"; + final var connection = MockupConnection.createConnectionConfig("test-conn", "", "http://localhost:8080/sparql"); + final var duration = Duration.of(2, ChronoUnit.SECONDS); + final var stream = new ByteArrayInputStream(content.getBytes()); + final var requestHeader = "application/sparql-results+json"; + + final var requestFactory = new SPARQLProtocolWorker.RequestFactory(type); + final var request = requestFactory.buildHttpRequest( + stream, + duration, + connection, + requestHeader + ); + + switch (type) { + case GET_QUERY -> assertEquals(connection.endpoint() + "?query=" + URLEncoder.encode(content, StandardCharsets.UTF_8), request.uri().toString()); + case POST_QUERY -> { + assertEquals("application/sparql-query", request.headers().firstValue("Content-Type").get()); + assertEquals("http://localhost:8080/sparql", request.uri().toString()); + assertTrue(request.bodyPublisher().isPresent()); + String body = request.bodyPublisher().map(p -> { + var bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); + var flowSubscriber = new StringSubscriber(bodySubscriber); + p.subscribe(flowSubscriber); + return bodySubscriber.getBody().toCompletableFuture().join(); + }).get(); + assertEquals(content, body); + } + case POST_UPDATE -> { + assertEquals("application/sparql-update", request.headers().firstValue("Content-Type").get()); + assertEquals("http://localhost:8080/sparql", request.uri().toString()); + assertTrue(request.bodyPublisher().isPresent()); + String body = request.bodyPublisher().map(p -> { + var bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); + var flowSubscriber = new StringSubscriber(bodySubscriber); + p.subscribe(flowSubscriber); + return bodySubscriber.getBody().toCompletableFuture().join(); + }).get(); + assertEquals(content, body); + } + case POST_URL_ENC_QUERY -> { + assertEquals("application/x-www-form-urlencoded", request.headers().firstValue("Content-Type").get()); + assertEquals("http://localhost:8080/sparql", request.uri().toString()); + assertTrue(request.bodyPublisher().isPresent()); + String body = request.bodyPublisher().map(p -> { + var bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); + var flowSubscriber = new StringSubscriber(bodySubscriber); + p.subscribe(flowSubscriber); + return bodySubscriber.getBody().toCompletableFuture().join(); + }).get(); + assertEquals("query=" + URLEncoder.encode(content, StandardCharsets.UTF_8), body); + } + case POST_URL_ENC_UPDATE -> { + assertEquals("application/x-www-form-urlencoded", request.headers().firstValue("Content-Type").get()); + assertEquals("http://localhost:8080/sparql", request.uri().toString()); + assertTrue(request.bodyPublisher().isPresent()); + String body = request.bodyPublisher().map(p -> { + var bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); + var flowSubscriber = new StringSubscriber(bodySubscriber); + p.subscribe(flowSubscriber); + return bodySubscriber.getBody().toCompletableFuture().join(); + }).get(); + assertEquals("update=" + URLEncoder.encode(content, StandardCharsets.UTF_8), body); + } + } + } +} diff --git a/src/test/java/org/aksw/iguana/cc/worker/impl/SPARQLProtocolWorkerTest.java b/src/test/java/org/aksw/iguana/cc/worker/impl/SPARQLProtocolWorkerTest.java new file mode 100644 index 000000000..31641287e --- /dev/null +++ b/src/test/java/org/aksw/iguana/cc/worker/impl/SPARQLProtocolWorkerTest.java @@ -0,0 +1,258 @@ +package org.aksw.iguana.cc.worker.impl; + +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.http.Fault; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import org.aksw.iguana.cc.config.elements.ConnectionConfig; +import org.aksw.iguana.cc.config.elements.DatasetConfig; +import org.aksw.iguana.cc.query.handler.QueryHandler; +import org.aksw.iguana.cc.worker.HttpWorker; +import org.aksw.iguana.cc.worker.ResponseBodyProcessor; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +public class SPARQLProtocolWorkerTest { + + @RegisterExtension + public static WireMockExtension wm = WireMockExtension.newInstance() + .options(new WireMockConfiguration().useChunkedTransferEncoding(Options.ChunkedEncodingPolicy.NEVER).dynamicPort().notifier(new Slf4jNotifier(true))) + .failOnUnmatchedRequests(true) + .build(); + + private final static String QUERY = "SELECT * WHERE { ?s ?p ?o }"; + private final static int QUERY_MIXES = 1; + private static Path queryFile; + + @BeforeAll + public static void setup() throws IOException { + queryFile = Files.createTempFile("iguana-test-queries", ".tmp"); + Files.writeString(queryFile, QUERY, StandardCharsets.UTF_8); + } + + @BeforeEach + public void reset() { + wm.resetMappings(); // reset stubbing maps after each test + } + + @AfterAll + public static void cleanup() throws IOException { + Files.deleteIfExists(queryFile); + } + + public static Stream> requestFactoryData() throws IOException, URISyntaxException { + final var uri = new URI("http://localhost:" + wm.getPort() + "/ds/query"); + + final var processor = new ResponseBodyProcessor("application/sparql-results+json"); + final var format = QueryHandler.Config.Format.SEPARATOR; + final var queryHandlder = new QueryHandler(new QueryHandler.Config(queryFile.toAbsolutePath().toString(), format, null, true, QueryHandler.Config.Order.LINEAR, 0L, QueryHandler.Config.Language.SPARQL)); + final var datasetConfig = new DatasetConfig("TestDS", null); + final var connection = new ConnectionConfig("TestConn", "1", datasetConfig, uri, new ConnectionConfig.Authentication("testUser", "password"), null, null); + final var workers = new ArrayDeque>(); + int i = 0; + for (var requestType : SPARQLProtocolWorker.RequestFactory.RequestType.values()) { + final var config = new SPARQLProtocolWorker.Config( + 1, + queryHandlder, + new HttpWorker.QueryMixes(QUERY_MIXES), + connection, + Duration.parse("PT100S"), + "application/sparql-results+json", + requestType, + false + ); + workers.add(Named.of(config.requestType().name(), new SPARQLProtocolWorker(i++, processor, config))); + } + return workers.stream(); + } + + public static List completionTargets() { + final var out = new ArrayList(); + final var queryMixesAmount = List.of(1, 2, 5, 10, 100, 1000); + final var timeDurations = List.of(Duration.of(1, ChronoUnit.SECONDS), Duration.of(5, ChronoUnit.SECONDS)); + + for (var queryMixes : queryMixesAmount) { + out.add(Arguments.of(new HttpWorker.QueryMixes(queryMixes))); + } + + for (var duration : timeDurations) { + out.add(Arguments.of(new HttpWorker.TimeLimit(duration))); + } + + return out; + } + + @ParameterizedTest(name = "[{index}] requestType = {0}") + @MethodSource("requestFactoryData") + @DisplayName("Test Request Factory") + public void testRequestFactory(SPARQLProtocolWorker worker) { + switch (worker.config().requestType()) { + case GET_QUERY -> wm.stubFor(get(urlPathEqualTo("/ds/query")) + .withQueryParam("query", equalTo(QUERY)) + .withBasicAuth("testUser", "password") + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body"))); + case POST_QUERY -> { + wm.stubFor(post(urlPathEqualTo("/ds/query")) + .withHeader("Content-Type", equalTo("application/sparql-query")) + .withHeader("Transfer-Encoding", equalTo("chunked")) + .withBasicAuth("testUser", "password") + .withRequestBody(equalTo(QUERY)) + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body"))); + return; + } + case POST_UPDATE -> { + wm.stubFor(post(urlPathEqualTo("/ds/query")) + .withHeader("Content-Type", equalTo("application/sparql-update")) + .withHeader("Transfer-Encoding", equalTo("chunked")) + .withBasicAuth("testUser", "password") + .withRequestBody(equalTo(QUERY)) + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body"))); + return; // TODO: wiremock behaves really weirdly when the request body is streamed + } + + case POST_URL_ENC_QUERY -> wm.stubFor(post(urlPathEqualTo("/ds/query")) + .withHeader("Content-Type", equalTo("application/x-www-form-urlencoded")) + .withBasicAuth("testUser", "password") + .withRequestBody(equalTo("query=" + URLEncoder.encode(QUERY, StandardCharsets.UTF_8))) + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body"))); + case POST_URL_ENC_UPDATE -> wm.stubFor(post(urlPathEqualTo("/ds/query")) + .withHeader("Content-Type", equalTo("application/x-www-form-urlencoded")) + .withBasicAuth("testUser", "password") + .withRequestBody(equalTo("update=" + URLEncoder.encode(QUERY, StandardCharsets.UTF_8))) + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body"))); + } + + final HttpWorker.Result result = worker.start().join(); + + assertEquals(result.executionStats().size(), QUERY_MIXES, "Worker should have executed only 1 query"); + assertNull(result.executionStats().get(0).error().orElse(null), "Worker threw an exception, during execution"); + assertEquals(200, result.executionStats().get(0).httpStatusCode().get(), "Worker returned wrong status code"); + assertNotEquals(0, result.executionStats().get(0).responseBodyHash().getAsLong(), "Worker didn't return a response body hash"); + assertEquals("Non-Empty-Body".getBytes(StandardCharsets.UTF_8).length, result.executionStats().get(0).contentLength().getAsLong(), "Worker returned wrong content length"); + assertNotEquals(Duration.ZERO, result.executionStats().get(0).duration(), "Worker returned zero duration"); + } + + @DisplayName("Test Malformed Response Processing") + @ParameterizedTest(name = "[{index}] fault = {0}") + @EnumSource(Fault.class) + public void testMalformedResponseProcessing(Fault fault) throws IOException, URISyntaxException { + SPARQLProtocolWorker worker = (SPARQLProtocolWorker) requestFactoryData().toList().get(0).getPayload(); + wm.stubFor(get(urlPathEqualTo("/ds/query")) + .willReturn(aResponse().withFault(fault))); + final HttpWorker.Result result = worker.start().join(); + assertEquals(1, result.executionStats().size()); + assertNotNull(result.executionStats().get(0).error().orElse(null)); + } + + @Test + public void testBadHttpCodeResponse() throws IOException, URISyntaxException { + SPARQLProtocolWorker worker = (SPARQLProtocolWorker) requestFactoryData().toList().get(0).getPayload(); + wm.stubFor(get(urlPathEqualTo("/ds/query")) + .willReturn(aResponse().withStatus(404))); + final HttpWorker.Result result = worker.start().join(); + assertEquals(1, result.executionStats().size()); + assertTrue(result.executionStats().get(0).httpError()); + } + + @ParameterizedTest + @MethodSource("completionTargets") + public void testCompletionTargets(HttpWorker.CompletionTarget target) throws URISyntaxException, IOException { + final var uri = new URI("http://localhost:" + wm.getPort() + "/ds/query"); + final var processor = new ResponseBodyProcessor("application/sparql-results+json"); + final var queryHandlder = new QueryHandler(new QueryHandler.Config(queryFile.toAbsolutePath().toString(), QueryHandler.Config.Format.SEPARATOR, null, true, QueryHandler.Config.Order.LINEAR, 0L, QueryHandler.Config.Language.SPARQL)); + final var datasetConfig = new DatasetConfig("TestDS", null); + final var connection = new ConnectionConfig("TestConn", "1", datasetConfig, uri, new ConnectionConfig.Authentication("testUser", "password"), null, null); + + final var config = new SPARQLProtocolWorker.Config( + 1, + queryHandlder, + target, + connection, + Duration.parse("PT20S"), + "application/sparql-results+json", + SPARQLProtocolWorker.RequestFactory.RequestType.POST_URL_ENC_QUERY, + false + ); + + SPARQLProtocolWorker worker = new SPARQLProtocolWorker(0, processor, config); + wm.stubFor(post(urlPathEqualTo("/ds/query")) + .withHeader("Content-Type", equalTo("application/x-www-form-urlencoded")) + .withBasicAuth("testUser", "password") + .withRequestBody(equalTo("query=" + URLEncoder.encode(QUERY, StandardCharsets.UTF_8))) + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body"))); + + final HttpWorker.Result result = worker.start().join(); + + for (var stat : result.executionStats()) { + assertTrue(stat.successful()); + assertTrue(stat.error().isEmpty()); + assertEquals(200, stat.httpStatusCode().orElseThrow()); + assertTrue(stat.contentLength().orElseThrow() > 0); + assertTrue(stat.duration().compareTo(Duration.ZERO) > 0); + } + + if (target instanceof HttpWorker.TimeLimit) { + Duration totalDuration = result.executionStats().stream() + .map(HttpWorker.ExecutionStats::duration) + .reduce(Duration::plus) + .get(); + + assertTrue(totalDuration.compareTo(((HttpWorker.TimeLimit) target).duration()) <= 0); + } else { + assertEquals(((HttpWorker.QueryMixes) target).number(), result.executionStats().size()); + } + } + + @Test + public void testTimeLimitExecutionCutoff() throws URISyntaxException, IOException { + final var uri = new URI("http://localhost:" + wm.getPort() + "/ds/query"); + + final var processor = new ResponseBodyProcessor("application/sparql-results+json"); + final var queryHandlder = new QueryHandler(new QueryHandler.Config(queryFile.toAbsolutePath().toString(), QueryHandler.Config.Format.SEPARATOR, null, true, QueryHandler.Config.Order.LINEAR, 0L, QueryHandler.Config.Language.SPARQL)); + final var datasetConfig = new DatasetConfig("TestDS", null); + final var connection = new ConnectionConfig("TestConn", "1", datasetConfig, uri, new ConnectionConfig.Authentication("testUser", "password"), null, null); + + final var config = new SPARQLProtocolWorker.Config( + 1, + queryHandlder, + new HttpWorker.TimeLimit(Duration.of(2, ChronoUnit.SECONDS)), + connection, + Duration.parse("PT20S"), + "application/sparql-results+json", + SPARQLProtocolWorker.RequestFactory.RequestType.POST_URL_ENC_QUERY, + false + ); + + SPARQLProtocolWorker worker = new SPARQLProtocolWorker(0, processor, config); + wm.stubFor(post(urlPathEqualTo("/ds/query")) + .withHeader("Content-Type", equalTo("application/x-www-form-urlencoded")) + .withBasicAuth("testUser", "password") + .withRequestBody(equalTo("query=" + URLEncoder.encode(QUERY, StandardCharsets.UTF_8))) + .willReturn(aResponse().withStatus(200).withBody("Non-Empty-Body").withFixedDelay(1000))); + + final HttpWorker.Result result = worker.start().join(); + assertEquals(1, result.executionStats().size()); // because of the delay, only one query should be executed + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/commons/factory/AnnotatedFactorizedObject.java b/src/test/java/org/aksw/iguana/commons/factory/AnnotatedFactorizedObject.java deleted file mode 100644 index 5913230c2..000000000 --- a/src/test/java/org/aksw/iguana/commons/factory/AnnotatedFactorizedObject.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.aksw.iguana.commons.factory; - -import org.aksw.iguana.commons.annotation.Nullable; -import org.aksw.iguana.commons.annotation.ParameterNames; -import org.aksw.iguana.commons.annotation.Shorthand; - -@Shorthand(value = "facto") -public class AnnotatedFactorizedObject extends FactorizedObject { - public AnnotatedFactorizedObject(String[] args, String[] args2) { - this.setArgs(args); - this.setArgs2(args2); - } - - @ParameterNames(names={"a","b","c"}) - public AnnotatedFactorizedObject(String a, String b, String c) { - this.setArgs(new String[] {a, b, c}); - } - - @ParameterNames(names={"a","b"}) - public AnnotatedFactorizedObject(String a, @Nullable String b) { - this.setArgs(new String[] {a, b==null?"wasNull":b}); - } - - public AnnotatedFactorizedObject() { - args = new String[] {"a3", "b3"}; - } - -} diff --git a/src/test/java/org/aksw/iguana/commons/factory/FactorizedObject.java b/src/test/java/org/aksw/iguana/commons/factory/FactorizedObject.java deleted file mode 100644 index e6f954a60..000000000 --- a/src/test/java/org/aksw/iguana/commons/factory/FactorizedObject.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.aksw.iguana.commons.factory; - -import org.aksw.iguana.commons.annotation.Nullable; - -public class FactorizedObject { - - protected String[] args; - protected String[] args2; - - public FactorizedObject(String[] args, String[] args2) { - this.setArgs(args); - this.setArgs2(args2); - } - - public FactorizedObject(String a, String b, String c) { - this.setArgs(new String[] {a, b, c}); - } - - public FactorizedObject(String a, @Nullable String b) { - this.setArgs(new String[] {a, b==null?"wasNull":b}); - } - - - public FactorizedObject() { - args = new String[] {"a3", "b3"}; - } - - /** - * @return the args - */ - public String[] getArgs() { - return args; - } - - /** - * @param args the args to set - */ - public void setArgs(String[] args) { - this.args = args; - } - - /** - * @return the args2 - */ - public String[] getArgs2() { - return args2; - } - - /** - * @param args2 the args2 to set - */ - public void setArgs2(String[] args2) { - this.args2 = args2; - } - -} diff --git a/src/test/java/org/aksw/iguana/commons/factory/TypedFactoryTest.java b/src/test/java/org/aksw/iguana/commons/factory/TypedFactoryTest.java deleted file mode 100644 index 3e27832dd..000000000 --- a/src/test/java/org/aksw/iguana/commons/factory/TypedFactoryTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.aksw.iguana.commons.factory; - -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -public class TypedFactoryTest { - - @Test - public void argumentClassesTest() { - String[] args = new String[]{"a1", "b1"}; - String[] args2 = new String[]{"a2", "b2"}; - TypedFactory factory = new TypedFactory<>(); - FactorizedObject testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", - new Object[]{args, args2}, new Class[]{String[].class, String[].class}); - assertEquals(args[0], testObject.getArgs()[0]); - assertEquals(args[1], testObject.getArgs()[1]); - assertEquals(args2[0], testObject.getArgs2()[0]); - assertEquals(args2[1], testObject.getArgs2()[1]); - - } - - - @Test - public void noConstructor() { - TypedFactory factory = new TypedFactory<>(); - HashMap map = new HashMap<>(); - map.put("nope", "nope"); - assertNull(factory.create("org.aksw.iguana.commons.factory.FactorizedObject", map)); - assertNull(factory.create("org.aksw.iguana.commons.factory.FactorizedObject", new Object[]{"nope"})); - assertNull(factory.create("org.aksw.iguana.commons.factory.FactorizedObject", new Object[]{"nope"}, new Class[]{String.class})); - assertNull(factory.createAnnotated("org.aksw.iguana.commons.factory.AnnotatedFactorizedObject", map)); - - map.clear(); - map.put("a", 123); - map.put("b", true); - assertNull(factory.create("org.aksw.iguana.commons.factory.FactorizedObject", map)); - assertNull(factory.createAnnotated("org.aksw.iguana.commons.factory.AnnotatedFactorizedObject", map)); - - } - - @Test - public void nullConstructorClass() { - TypedFactory factory = new TypedFactory<>(); - FactorizedObject testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", new Object[]{"a", "b", "c"}, null); - assertEquals("a", testObject.getArgs()[0]); - assertEquals("b", testObject.getArgs()[1]); - assertEquals("c", testObject.getArgs()[2]); - testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", null, null); - assertEquals("a3", testObject.getArgs()[0]); - assertEquals("b3", testObject.getArgs()[1]); - } - - @Test - public void nullClass() { - TypedFactory factory = new TypedFactory<>(); - assertNull(factory.create(null, new HashMap<>())); - assertNull(factory.create(null, new Object[]{})); - assertNull(factory.create(null, new Object[]{}, new Class[]{})); - - } - - @Test - public void classNameNotFoundTest() { - TypedFactory factory = new TypedFactory<>(); - assertNull(factory.create("thisClassShouldNotExist", new HashMap<>())); - assertNull(factory.create("thisClassShouldNotExist", new Object[]{})); - assertNull(factory.create("thisClassShouldNotExist", new Object[]{}, new Class[]{})); - assertNull(factory.createAnnotated("thisClassShouldNotExist", new HashMap<>())); - } - - @Test - public void argumentStringsTest() { - - TypedFactory factory = new TypedFactory<>(); - FactorizedObject testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", (Object[]) null); - assertEquals("a3", testObject.getArgs()[0]); - assertEquals("b3", testObject.getArgs()[1]); - } - - - @Test - public void mapCreationTestParameterNames() { - - TypedFactory factory = new TypedFactory<>(); - Map arguments = new HashMap<>(); - arguments.put("a", "a4"); - arguments.put("b", "b4"); - arguments.put("c", "c4"); - FactorizedObject testObject = factory.createAnnotated("org.aksw.iguana.commons.factory.AnnotatedFactorizedObject", arguments); - assertEquals("a4", testObject.getArgs()[0]); - assertEquals("b4", testObject.getArgs()[1]); - assertEquals("c4", testObject.getArgs()[2]); - arguments.clear(); - arguments.put("a", "a5"); - testObject = factory.createAnnotated("org.aksw.iguana.commons.factory.AnnotatedFactorizedObject", arguments); - assertEquals("a5", testObject.getArgs()[0]); - assertEquals("wasNull", testObject.getArgs()[1]); - } - - @Test - public void testNullable() { - - TypedFactory factory = new TypedFactory<>(); - Map arguments = new HashMap<>(); - arguments.put("a", "a4"); - arguments.put("b", "b4"); - FactorizedObject testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", arguments); - assertEquals("a4", testObject.getArgs()[0]); - assertEquals("b4", testObject.getArgs()[1]); - arguments.remove("b"); - testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", arguments); - assertEquals("a4", testObject.getArgs()[0]); - assertEquals("wasNull", testObject.getArgs()[1]); - - } - - @Test - public void mapCreationTest() { - - TypedFactory factory = new TypedFactory<>(); - Map arguments = new HashMap<>(); - arguments.put("a", "a4"); - arguments.put("b", "b4"); - arguments.put("c", "c4"); - FactorizedObject testObject = factory.create("org.aksw.iguana.commons.factory.FactorizedObject", arguments); - assertEquals("a4", testObject.getArgs()[0]); - assertEquals("b4", testObject.getArgs()[1]); - assertEquals("c4", testObject.getArgs()[2]); - - } - - @Test - public void shortHandAnnotationTest() { - - TypedFactory factory = new TypedFactory<>(); - Map arguments = new HashMap<>(); - arguments.put("a", "a4"); - arguments.put("b", "b4"); - arguments.put("c", "c4"); - AnnotatedFactorizedObject testObject = factory.create("facto", arguments); - assertEquals("a4", testObject.getArgs()[0]); - assertEquals("b4", testObject.getArgs()[1]); - assertEquals("c4", testObject.getArgs()[2]); - - } - -} diff --git a/src/test/java/org/aksw/iguana/commons/io/BigByteArrayInputStreamTest.java b/src/test/java/org/aksw/iguana/commons/io/BigByteArrayInputStreamTest.java new file mode 100644 index 000000000..cb68b1b82 --- /dev/null +++ b/src/test/java/org/aksw/iguana/commons/io/BigByteArrayInputStreamTest.java @@ -0,0 +1,224 @@ +package org.aksw.iguana.commons.io; + +import com.google.common.primitives.Bytes; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +@Disabled("This test takes a lot of time and resources.") +class BigByteArrayInputStreamTest { + + private static final int MAX_SINGLE_BUFFER_SIZE = Integer.MAX_VALUE - 8; + private static Random rng = new Random(); + + /** + * Creates a random 2d-array buffer with the given size. + * + * @param size number of bytes + * @param maxSingleBufferSize maximum size of a single array + * @return 2d-array buffer + */ + public static byte[][] getBigRandomBuffer(long size, int maxSingleBufferSize) { + if (size < 1) + return new byte[0][0]; + final var bufferField = new byte[(int) ((size - 1) / maxSingleBufferSize) + 1][]; + for (int i = 0; i < bufferField.length; i++) { + final var bufferSize = (size > maxSingleBufferSize) ? maxSingleBufferSize : (int) size; + bufferField[i] = new byte[bufferSize]; + rng.nextBytes(bufferField[i]); + size -= bufferSize; + } + return bufferField; + } + + @Test + @DisplayName("Test illegal arguments") + public void testIllegalArguments() throws IOException { + final var bbaos = new BigByteArrayOutputStream(100); + final var data = 1; + bbaos.write(data); + final var bbais = new BigByteArrayInputStream(bbaos); + + assertThrows(NullPointerException.class, () -> bbais.readNBytes(null, 0, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.readNBytes(new byte[1], -1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.readNBytes(new byte[1], 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.readNBytes(new byte[1], 0, 2)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.readNBytes(new byte[1], 1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.readNBytes(new byte[1], 2, 0)); + assertThrows(NullPointerException.class, () -> bbais.read(null, 0, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.read(new byte[1], -1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.read(new byte[1], 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.read(new byte[1], 0, 2)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.read(new byte[1], 1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbais.read(new byte[1], 2, 0)); + + assertThrows(NullPointerException.class, () -> new BigByteArrayInputStream((byte[]) null)); + assertThrows(NullPointerException.class, () -> new BigByteArrayInputStream((BigByteArrayOutputStream) null)); + } + + @Test + @DisplayName("Test read method with big data") + public void testBigRead() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var buffer = getBigRandomBuffer(((long) MAX_SINGLE_BUFFER_SIZE) + 1000L, MAX_SINGLE_BUFFER_SIZE - 1); + bbaos.write(buffer); + final var bbais = new BigByteArrayInputStream(bbaos); + + assertArrayEquals(buffer[0], bbais.readNBytes(MAX_SINGLE_BUFFER_SIZE - 1)); + assertArrayEquals(buffer[1], bbais.readNBytes(MAX_SINGLE_BUFFER_SIZE - 1)); + } + + @Test + @DisplayName("Test read method with small data") + public void testSmallRead() throws IOException { + final var bbaos = new BigByteArrayOutputStream(100); + final var data = 1; + bbaos.write(data); + final var bbais = new BigByteArrayInputStream(bbaos); + assertEquals(data, bbais.read()); + assertEquals(-1, bbais.read()); + } + + @Test + @DisplayName("Test allBytes() method throws exception") + public void testReadAllBytesException() throws IOException { + final var bbais = new BigByteArrayInputStream(new byte[]{ 1,2,3,4 }); + assertThrows(IOException.class, () -> bbais.readAllBytes()); + } + + @Test + @DisplayName("Test readNBytes(len) method") + public void testReadMethods1() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var buffer = getBigRandomBuffer(1000, MAX_SINGLE_BUFFER_SIZE); + bbaos.write(buffer); + final var bbais = new BigByteArrayInputStream(bbaos); + + assertArrayEquals(Arrays.copyOfRange(buffer[0], 0, 500), bbais.readNBytes(500)); + assertArrayEquals(Arrays.copyOfRange(buffer[0], 500, 1000), bbais.readNBytes(510)); + assertArrayEquals(new byte[0], bbais.readNBytes(1)); + assertEquals(-1, bbais.read()); + } + + @Test + @DisplayName("Test readNBytes(buffer, off, len) method") + public void testReadMethods2() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var data = getBigRandomBuffer(210, MAX_SINGLE_BUFFER_SIZE); + bbaos.write(data); + final var bbais = new BigByteArrayInputStream(bbaos); + + final var buffer = new byte[100]; + assertEquals(100, bbais.readNBytes(buffer, 0, 100)); + assertArrayEquals(Arrays.copyOfRange(data[0], 0, 100), buffer); + assertEquals(50, bbais.readNBytes(buffer, 0, 50)); + assertEquals(50, bbais.readNBytes(buffer, 50, 50)); + assertArrayEquals(Arrays.copyOfRange(data[0], 100, 200), buffer); + assertEquals(10, bbais.readNBytes(buffer, 0, 100)); + assertArrayEquals(Arrays.copyOfRange(data[0], 200, 210), Arrays.copyOfRange(buffer, 0, 10)); + assertEquals(0, bbais.readNBytes(buffer, 0, 100)); + } + + @Test + @DisplayName("Test read(buffer, off, len) method") + public void testReadMethods3() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var data = getBigRandomBuffer(210, MAX_SINGLE_BUFFER_SIZE); + bbaos.write(data); + final var bbais = new BigByteArrayInputStream(bbaos); + + final var buffer = new byte[100]; + assertEquals(100, bbais.read(buffer, 0, 100)); + assertArrayEquals(Arrays.copyOfRange(data[0], 0, 100), buffer); + assertEquals(50, bbais.read(buffer, 0, 50)); + assertEquals(50, bbais.read(buffer, 50, 50)); + assertArrayEquals(Arrays.copyOfRange(data[0], 100, 200), buffer); + assertEquals(10, bbais.read(buffer, 0, 100)); + assertArrayEquals(Arrays.copyOfRange(data[0], 200, 210), Arrays.copyOfRange(buffer, 0, 10)); + assertEquals(-1, bbais.read(buffer, 0, 100)); + } + + @Test + @DisplayName("Test read(buffer) method") + public void testReadMethods4() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var data = getBigRandomBuffer(110, MAX_SINGLE_BUFFER_SIZE); + bbaos.write(data); + final var bbais = new BigByteArrayInputStream(bbaos); + + assertEquals(0, bbais.read(new byte[0])); + final var buffer = new byte[100]; + assertEquals(100, bbais.read(buffer)); + assertArrayEquals(Arrays.copyOfRange(data[0], 0, 100), buffer); + assertEquals(10, bbais.read(buffer)); + assertArrayEquals(Arrays.copyOfRange(data[0], 100, 110), Arrays.copyOfRange(buffer, 0 , 10)); + assertEquals(-1, bbais.read(buffer)); + } + + @Test + @DisplayName("Test read() method") + public void testReadMethods5() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var data = "test".getBytes(StandardCharsets.UTF_8); + bbaos.write(data); + final var bbais = new BigByteArrayInputStream(bbaos); + + List buffer = new ArrayList<>(); + byte currentByte; + while ((currentByte = (byte) bbais.read()) != -1) { + buffer.add(currentByte); + } + assertEquals("test", new String(Bytes.toArray(buffer), StandardCharsets.UTF_8)); + } + + + @Test + @DisplayName("Test bbaos is closed after reading") + public void testBbaosIsClosed() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + bbaos.write(new byte[] { 1, 2, 3, 4 }); + final var bbais = new BigByteArrayInputStream(bbaos); + assertEquals(1, bbais.read()); + assertEquals(2, bbais.read()); + assertEquals(3, bbais.read()); + assertEquals(4, bbais.read()); + assertEquals(-1, bbais.read()); + assertThrows(IOException.class, () -> bbaos.write("test".getBytes())); + } + + @Test + @DisplayName("Test skip() method with small data") + public void testSmallSkip() throws IOException { + final var bigBuffer = getBigRandomBuffer(400, MAX_SINGLE_BUFFER_SIZE); + final var bbaos = new BigByteArrayOutputStream(); + bbaos.write(bigBuffer); + final var bbais = new BigByteArrayInputStream(bbaos); + assertEquals(100, bbais.skip(100)); + assertArrayEquals(Arrays.copyOfRange(bigBuffer[0], 100, 200), bbais.readNBytes(100)); + assertEquals(200, bbais.skip(200)); + assertEquals(-1, bbais.read()); + assertEquals(0, bbais.skip(100)); + } + + @Test + @DisplayName("Test skip() method with big data") + public void testBigSkip() throws IOException { + final var bigBuffer = getBigRandomBuffer(((long) MAX_SINGLE_BUFFER_SIZE) * 2L, MAX_SINGLE_BUFFER_SIZE); + final var bbaos = new BigByteArrayOutputStream(); + bbaos.write(bigBuffer); + final var bbais = new BigByteArrayInputStream(bbaos); + assertEquals((MAX_SINGLE_BUFFER_SIZE * 2L) - 4, bbais.skip((MAX_SINGLE_BUFFER_SIZE * 2L) - 4)); + assertArrayEquals(Arrays.copyOfRange(bigBuffer[1], MAX_SINGLE_BUFFER_SIZE - 4, MAX_SINGLE_BUFFER_SIZE - 2), bbais.readNBytes(2)); + assertEquals(2, bbais.skip(200)); + assertEquals(-1, bbais.read()); + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/commons/io/BigByteArrayOutputStreamTest.java b/src/test/java/org/aksw/iguana/commons/io/BigByteArrayOutputStreamTest.java new file mode 100644 index 000000000..5b49c0541 --- /dev/null +++ b/src/test/java/org/aksw/iguana/commons/io/BigByteArrayOutputStreamTest.java @@ -0,0 +1,310 @@ +package org.aksw.iguana.commons.io; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +@Disabled("This test takes a lot of time and resources.") +class BigByteArrayOutputStreamTest { + final static Random rng = new Random(0); + + public static List data() { + final long maxSize = Integer.MAX_VALUE - 8; + + final Supplier sup1 = () -> getBigRandomBuffer(10L, (int) maxSize); + final Supplier sup2 = () -> getBigRandomBuffer(maxSize * 2L, (int) maxSize); + + return List.of( + Arguments.of(Named.of(String.valueOf(10), sup1), 10, new int[] { 10 }), + Arguments.of(Named.of(String.valueOf(10), sup1), maxSize * 2L, new int[] {(int) maxSize, (int) maxSize}), // small data, high initial capacity + Arguments.of(Named.of(String.valueOf(maxSize * 2L), sup2), maxSize * 2L, new int[] {(int) maxSize, (int) maxSize}), + Arguments.of(Named.of(String.valueOf(maxSize * 2L), sup2), 0, new int[] {(int) maxSize, (int) maxSize}) + ); + } + + /** + * Creates a random 2d-array buffer with the given size. + * + * @param size number of bytes + * @param maxSingleBufferSize maximum size of a single array + * @return 2d-array buffer + */ + public static byte[][] getBigRandomBuffer(long size, int maxSingleBufferSize) { + if (size < 1) + return new byte[0][0]; + final var bufferField = new byte[(int) ((size - 1) / maxSingleBufferSize) + 1][]; + for (int i = 0; i < bufferField.length; i++) { + final var bufferSize = (size > maxSingleBufferSize) ? maxSingleBufferSize : (int) size; + bufferField[i] = new byte[bufferSize]; + rng.nextBytes(bufferField[i]); + size -= bufferSize; + } + return bufferField; + } + + @Test + public void testClose() throws IOException { + final var bbaos = new BigByteArrayOutputStream(); + final var testData = "test123".getBytes(StandardCharsets.UTF_8); + bbaos.write(testData); + bbaos.close(); + assertThrows(IOException.class, () -> bbaos.clear()); + assertThrows(IOException.class, () -> bbaos.reset()); + assertThrows(IOException.class, () -> bbaos.write(1)); + assertThrows(IOException.class, () -> bbaos.write((byte) 1)); + assertThrows(IOException.class, () -> bbaos.write(new byte[][] {{1}}) ); + assertThrows(IOException.class, () -> bbaos.write(new byte[] {1}, 0, 1)); + assertThrows(IOException.class, () -> bbaos.write(new byte[] {1})); + assertThrows(IOException.class, () -> bbaos.write((new BigByteArrayOutputStream(10)))); + assertEquals(testData.length, bbaos.size()); + assertArrayEquals(new byte[][] {testData} , bbaos.toByteArray()); + assertEquals(1, bbaos.getBaos().size()); + assertArrayEquals(testData, bbaos.getBaos().get(0).toByteArray()); + } + + @Test + @DisplayName("Test basic write operations") + public void testOtherWriteMethods() throws IOException { + final byte[] buffer = getBigRandomBuffer(10, 10)[0]; + + final var b2 = new byte[] { 0, 1, 2, 3 }; + int i = ByteBuffer.wrap(b2).getInt(); + + try (final var bbaos = new BigByteArrayOutputStream()) { + assertDoesNotThrow(() -> bbaos.write(buffer[0])); + assertEquals(1, bbaos.size()); + assertEquals(buffer[0], bbaos.toByteArray()[0][0]); + + assertDoesNotThrow(() -> bbaos.write(buffer, 1, 9)); + assertEquals(10, bbaos.size()); + assertArrayEquals(buffer, bbaos.toByteArray()[0]); + + final var bbaos2 = new BigByteArrayOutputStream(1); + assertDoesNotThrow(() -> bbaos2.write(bbaos)); + assertEquals(10, bbaos2.size()); + assertArrayEquals(buffer, bbaos2.toByteArray()[0]); + + assertDoesNotThrow(() -> bbaos2.write(i)); + assertEquals(11, bbaos2.size()); + assertEquals(b2[3], bbaos2.toByteArray()[0][10]); // low order byte + } + } + + @Test + @DisplayName("Test illegal capacity arguments") + public void testNegativeCapactiy() { + assertThrows(IllegalArgumentException.class, () -> new BigByteArrayOutputStream(-1)); + assertThrows(IllegalArgumentException.class, () -> new BigByteArrayOutputStream(-1L)); + } + + @Test + @DisplayName("Test illegal write arguments") + public void testIndexOutOfBounds() throws IOException { + try (final var bbaos = new BigByteArrayOutputStream()) { + final byte[] nullBuffer = null; + final var buffer = new byte[10]; + assertThrows(IndexOutOfBoundsException.class, () -> bbaos.write(buffer, -1, 10)); + assertThrows(IndexOutOfBoundsException.class, () -> bbaos.write(buffer, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> bbaos.write(buffer, 0, 11)); + assertThrows(NullPointerException.class, () -> bbaos.write(nullBuffer)); + } + } + + + @Test + @DisplayName("Test default constructor") + void testDefaultConstructor() throws IOException { + try (final var bbaos = new BigByteArrayOutputStream()) { + assertEquals(0, bbaos.size()); + assertEquals(1, bbaos.getBaos().size()); + assertEquals(0, bbaos.getBaos().get(0).size()); + assertDoesNotThrow(() -> bbaos.write("test".getBytes(StandardCharsets.UTF_8))); + assertEquals(1, bbaos.getBaos().size()); + assertEquals(4, bbaos.getBaos().get(0).size()); + assertEquals(4, bbaos.size()); + } + } + + @Test + @DisplayName("Test constructor with capacity argument") + void testConstructorWithInt() throws IOException { + try (final var bbaos = new BigByteArrayOutputStream(100)) { + assertEquals(0, bbaos.size()); + assertEquals(1, bbaos.getBaos().size()); + assertEquals(0, bbaos.getBaos().get(0).size()); + assertEquals(100, bbaos.getBaos().get(0).getBuffer().length); + assertDoesNotThrow(() -> bbaos.write("test".getBytes(StandardCharsets.UTF_8))); + assertEquals(4, bbaos.size()); + assertEquals(1, bbaos.getBaos().size()); + assertEquals(4, bbaos.getBaos().get(0).size()); + assertEquals(100, bbaos.getBaos().get(0).getBuffer().length); + } + } + + @Test + @DisplayName("Test constructor with big capacity argument") + void testConstructorWithBigLong() throws IOException { + try (final var bbaos = new BigByteArrayOutputStream(((long) Integer.MAX_VALUE) + 10)) { + assertEquals(0, bbaos.size()); + assertEquals(2, bbaos.getBaos().size()); + assertEquals(0, bbaos.getBaos().get(0).size()); + assertEquals(0, bbaos.getBaos().get(1).size()); + assertNotEquals(0, bbaos.getBaos().get(0).getBuffer().length); // rough comparison + assertNotEquals(0, bbaos.getBaos().get(1).getBuffer().length); + assertDoesNotThrow(() -> bbaos.write("test".getBytes(StandardCharsets.UTF_8))); + assertEquals(4, bbaos.size()); + assertEquals(2, bbaos.getBaos().size()); + assertEquals(4, bbaos.getBaos().get(0).size()); + assertEquals(0, bbaos.getBaos().get(1).size()); + } + } + + @Test + @DisplayName("Test write method with big byte arrays") + void testBaosOverflow() throws IOException { + final var maxArraySize = Integer.MAX_VALUE - 8; + final var firstBufferSize = maxArraySize - 1; + final var secondBufferSize = 2; + try (final var bbaos = new BigByteArrayOutputStream(maxArraySize)) { + final var firstBuffer = getBigRandomBuffer(firstBufferSize, maxArraySize); + final var secondBuffer = getBigRandomBuffer(secondBufferSize, maxArraySize); + + assertEquals(0, bbaos.size()); + assertEquals(1, bbaos.getBaos().size()); + assertEquals(0, bbaos.getBaos().get(0).size()); + assertEquals(maxArraySize, bbaos.getBaos().get(0).getBuffer().length); + assertDoesNotThrow(() -> bbaos.write(firstBuffer)); + for (int i = 0; i < firstBufferSize; i++) { + assertEquals(firstBuffer[0][i], bbaos.getBaos().get(0).getBuffer()[i]); // save memory during execution of this test with this loop + } + assertEquals(firstBufferSize, bbaos.size()); + assertEquals(1, bbaos.getBaos().size()); + assertEquals(firstBufferSize, bbaos.getBaos().get(0).size()); + assertArrayEquals(firstBuffer, bbaos.toByteArray()); + + // overflow first baos + assertDoesNotThrow(() -> bbaos.write(secondBuffer)); + assertEquals(maxArraySize, bbaos.getBaos().get(1).getBuffer().length); + assertEquals(firstBufferSize + secondBufferSize, bbaos.size()); + assertEquals(2, bbaos.getBaos().size()); + assertEquals(maxArraySize, bbaos.getBaos().get(0).size()); + assertEquals(secondBufferSize - (maxArraySize - firstBufferSize), bbaos.getBaos().get(1).size()); + + // test content of first baos + for (int i = 0; i < firstBufferSize; i++) + assertEquals(firstBuffer[0][i], bbaos.getBaos().get(0).getBuffer()[i]); + for (int i = firstBufferSize; i < maxArraySize; i++) + assertEquals(secondBuffer[0][i - firstBufferSize], bbaos.getBaos().get(0).getBuffer()[i]); + + // test content of second baos + assertArrayEquals(Arrays.copyOfRange(secondBuffer[0], secondBufferSize - (maxArraySize - firstBufferSize), secondBufferSize), bbaos.getBaos().get(1).toByteArray()); + + // reset + bbaos.reset(); + assertEquals(2, bbaos.getBaos().size()); // baos won't be removed with reset + assertEquals(0, bbaos.size()); + assertEquals(0, bbaos.getBaos().get(0).size()); + assertEquals(0, bbaos.getBaos().get(1).size()); + assertEquals(maxArraySize, bbaos.getBaos().get(0).getBuffer().length); + assertEquals(maxArraySize, bbaos.getBaos().get(1).getBuffer().length); + + assertDoesNotThrow(() -> bbaos.write(firstBuffer)); + assertEquals(firstBufferSize, bbaos.size()); + assertEquals(firstBufferSize, bbaos.getBaos().get(0).size()); + for (int i = 0; i < firstBufferSize; i++) { + assertEquals(firstBuffer[0][i], bbaos.getBaos().get(0).getBuffer()[i]); + } + + assertDoesNotThrow(() -> bbaos.write(secondBuffer)); + assertEquals(2, bbaos.getBaos().size()); + assertEquals(maxArraySize, bbaos.getBaos().get(1).getBuffer().length); + assertEquals(firstBufferSize + secondBufferSize, bbaos.size()); + assertEquals(maxArraySize, bbaos.getBaos().get(0).size()); + assertEquals(secondBufferSize - (maxArraySize - firstBufferSize), bbaos.getBaos().get(1).size()); + for (int i = 0; i < firstBufferSize; i++) + assertEquals(firstBuffer[0][i], bbaos.getBaos().get(0).getBuffer()[i]); + for (int i = firstBufferSize; i < maxArraySize; i++) + assertEquals(secondBuffer[0][i - firstBufferSize], bbaos.getBaos().get(0).getBuffer()[i]); + + assertArrayEquals(Arrays.copyOfRange(secondBuffer[0], secondBufferSize - (maxArraySize - firstBufferSize), secondBufferSize), bbaos.getBaos().get(1).toByteArray()); + } + } + + @ParameterizedTest(name = "[{index}] randomBufferSize={0}, initialCapacitiy={1}, baosSizes={2}") + @MethodSource("data") + @DisplayName("Test reset method") + void testReset(Supplier bufferSup, long initialCapacitiy, int[] baosSizes) throws IOException { + final var buffer = bufferSup.get(); + try (final var bbaos = new BigByteArrayOutputStream(initialCapacitiy)) { + bbaos.write(buffer); + assertEquals(baosSizes.length, bbaos.getBaos().size()); // expected amount of baos + for (int i = 0; i < buffer.length; i++) { + assertArrayEquals(buffer[i], bbaos.getBaos().get(i).toByteArray()); // expected content + assertEquals(baosSizes[i], bbaos.getBaos().get(i).getBuffer().length); // expected baos sizes + } + assertEquals(Arrays.stream(buffer).mapToLong(x -> x.length).sum(), bbaos.size()); + + bbaos.reset(); + + assertEquals(0, bbaos.size()); + assertEquals(baosSizes.length, bbaos.getBaos().size()); // same amount of baos + for (int i = 0; i < buffer.length; i++) { + assertEquals(baosSizes[i], bbaos.getBaos().get(i).getBuffer().length); // baos sizes should be same + } + + // after clear, a new write should result same expected content and state + bbaos.write(buffer); + assertEquals(Arrays.stream(buffer).mapToLong(x -> x.length).sum(), bbaos.size()); + for (int i = 0; i < buffer.length; i++) { + assertArrayEquals(buffer[i], bbaos.getBaos().get(i).toByteArray()); // expected content + } + + // check baos sizes again after write + for (int i = 0; i < baosSizes.length; i++) { + assertEquals(baosSizes[i], bbaos.getBaos().get(i).getBuffer().length); + } + } + } + + @ParameterizedTest(name = "[{index}] randomBufferSize={0}, initialCapacitiy={1}, baosSizes={2}") + @MethodSource("data") + @DisplayName("Test clear method") + void testClear(Supplier bufferSup, long initialCapacitiy, int[] baosSizes) throws IOException { + final var buffer = bufferSup.get(); + try (final var bbaos = new BigByteArrayOutputStream(initialCapacitiy)) { + bbaos.write(buffer); + assertEquals(baosSizes.length, bbaos.getBaos().size()); // expected amount of baos + for (int i = 0; i < buffer.length; i++) { + assertArrayEquals(buffer[i], bbaos.getBaos().get(i).toByteArray()); // expected content + assertEquals(baosSizes[i], bbaos.getBaos().get(i).getBuffer().length); // expected baos sizes + } + assertEquals(Arrays.stream(buffer).mapToLong(x -> x.length).sum(), bbaos.size()); + + bbaos.clear(); + assertEquals(0, bbaos.size()); + assertEquals(1, bbaos.getBaos().size()); // deleted all baos except first one + assertEquals(baosSizes[0], bbaos.getBaos().get(0).getBuffer().length); // first baos maintained previous buffer size + + // after clear, a new write should result same expected content + bbaos.write(buffer); + for (int i = 0; i < buffer.length; i++) { + assertArrayEquals(buffer[i], bbaos.getBaos().get(i).toByteArray()); // expected content + } + assertEquals(Arrays.stream(buffer).mapToLong(x -> x.length).sum(), bbaos.size()); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/aksw/iguana/commons/number/NumberUtilsTest.java b/src/test/java/org/aksw/iguana/commons/number/NumberUtilsTest.java deleted file mode 100644 index fa77c09a2..000000000 --- a/src/test/java/org/aksw/iguana/commons/number/NumberUtilsTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.aksw.iguana.commons.number; - - -import org.aksw.iguana.commons.numbers.NumberUtils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static org.junit.Assert.assertEquals; - - -@RunWith(Parameterized.class) -public class NumberUtilsTest { - - @Parameterized.Parameters - public static Collection data() { - List testConfigs = new ArrayList(); - //simple method - testConfigs.add(new Object[]{"123", Long.class, 123L}); - testConfigs.add(new Object[]{"123.0", Double.class, 123.0}); - testConfigs.add(new Object[]{"123", Double.class, 123.0}); - testConfigs.add(new Object[]{"123.A", Double.class, null}); - testConfigs.add(new Object[]{"123.A", Long.class, null}); - testConfigs.add(new Object[]{"123.0123", Double.class, 123.0123}); - testConfigs.add(new Object[]{null, Double.class, null}); - testConfigs.add(new Object[]{null, Long.class, null}); - - return testConfigs; - } - - private String number; - private Class clazz; - private Number expected; - - public NumberUtilsTest(String number, Class clazz, Number expected){ - this.number=number; - this.expected = expected; - this.clazz=clazz; - } - - @Test - public void checkForClass(){ - if(clazz == Long.class){ - assertEquals(expected, NumberUtils.getLong(number)); - } - else if(clazz == Double.class) { - assertEquals(expected, NumberUtils.getDouble(number)); - - } - } - -} diff --git a/src/test/java/org/aksw/iguana/commons/script/ScriptExecutorTest.java b/src/test/java/org/aksw/iguana/commons/script/ScriptExecutorTest.java deleted file mode 100644 index 9c6959367..000000000 --- a/src/test/java/org/aksw/iguana/commons/script/ScriptExecutorTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.aksw.iguana.commons.script; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@RunWith(Parameterized.class) -public class ScriptExecutorTest { - - private static Logger LOGGER = LoggerFactory.getLogger(ScriptExecutorTest.class); - - private String cmd; - private String[] args; - private int expectedExitCode; - private Method callbackMethod; - private Object[] callbackArgs=new Object[]{}; - - @Parameterized.Parameters - public static Collection data() { - List testConfigs = new ArrayList(); - //simple method - testConfigs.add(new Object[]{"/bin/touch", new String[]{"ShouldNotExistWhatSoEver"}, 0, "removeFile", new Object[]{"ShouldNotExistWhatSoEver"}}); - //testing if additional arguments are checked - testConfigs.add(new Object[]{"/bin/echo test", new String[]{"123", "456"}, 0, "emptyCallback", new Object[]{}}); - //should fail as file not exist - testConfigs.add(new Object[]{"scriptThatShouldNotExist", new String[]{}, -1, "emptyCallback", new Object[]{}}); - //should fail with 1 - - - return testConfigs; - } - - - - public ScriptExecutorTest(String cmd, String[] args, int expectedExitCode, String callbackMethodName, Object[] callbackArgs) throws NoSuchMethodException { - this.cmd=cmd; - this.args=args; - this.expectedExitCode=expectedExitCode; - this.callbackArgs = callbackArgs; - Class[] classes = new Class[callbackArgs.length]; - for(int i=0;i