diff --git a/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/collector/Api.scala b/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/collector/Api.scala index 163502c..b4210a4 100644 --- a/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/collector/Api.scala +++ b/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/collector/Api.scala @@ -25,19 +25,19 @@ final case class Api(vendor: String, version: String) { } object Api { - private val GenI = Gen.const(Api("i", "")) - private val GenIce = Gen.const(Api("ice", ".png")) + val GenI = Gen.const(Api("i", "")) + val GenIce = Gen.const(Api("ice", ".png")) def fixedApis: Gen[Api] = Gen.oneOf(GenI, GenIce) - def genApi(nEvents: Int): Gen[Api] = - (nEvents match { + def genApi(apiType: Int): Gen[Api] = + (apiType match { case 0 => (genVendor, genVersion) - case 1 => (Gen.const("com.snowplowanalytics.snowplow"), Gen.oneOf("tp1", "tp2")) - case _ => (Gen.const("com.snowplowanalytics.snowplow"), Gen.const("tp2")) + case 1 => (Gen.const("com.snowplowanalytics.snowplow"), Gen.const("tp1")) + case 2 => (Gen.const("com.snowplowanalytics.snowplow"), Gen.const("tp2")) }).mapN(Api.apply) - private def genVendor = + def genVendor = for { venPartsN <- Gen.chooseNum(1, 5) venNs <- Gen.listOfN(venPartsN, Gen.chooseNum(1, 10)) @@ -45,7 +45,7 @@ object Api { sep <- Gen.oneOf("-", ".", "_", "~") } yield vendorParts.mkString(sep) - private def genVersion = + def genVersion = for { verN <- Gen.chooseNum(1, 10) version <- Gen.stringOfN(verN, Gen.alphaNumChar) diff --git a/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/tracker/HttpRequest.scala b/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/tracker/HttpRequest.scala index 52d094d..e6e9048 100644 --- a/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/tracker/HttpRequest.scala +++ b/core/src/main/scala/com/snowplowanalytics/snowplow/eventgen/tracker/HttpRequest.scala @@ -33,27 +33,74 @@ object HttpRequest { } case class MethodFrequencies( - get: Int, - post: Int, - head: Int -) + get: Int, + post: Int, + head: Int + ) + + case class PathFrequencies( + i: Int, + ice: Int, + tp1: Int, + tp2: Int, + random: Int, + providedPath: Option[ProvidedPathFrequency] + ) + + case class ProvidedPathFrequency( + vendor: String, + version: String, + frequency: Int + ) object Method { final case class Post(path: Api) extends Method final case class Get(path: Api) extends Method final case class Head(path: Api) extends Method - def gen(freq: MethodFrequencies): Gen[Method] = { + def gen(methodFreq: MethodFrequencies, pathFreq: PathFrequencies): Gen[Method] = { Gen.frequency( - (freq.post, genPost), - (freq.get, genGet), - (freq.head, genHead) + (methodFreq.post, genPost(pathFreq)), + (methodFreq.get, genGet(pathFreq)), + (methodFreq.head, genHead(pathFreq)) ) } - private def genPost: Gen[Method.Post] = Gen.oneOf(genApi(0), genApi(200)).map(Method.Post) - private def genGet: Gen[Method.Get] = Gen.oneOf(fixedApis, genApi(0), genApi(1)).map(Method.Get) - private def genHead: Gen[Method.Head] = Gen.oneOf(fixedApis, genApi(0), genApi(1)).map(Method.Head) + private def genPost(pathFreq: PathFrequencies): Gen[Method.Post] = + Gen.frequency( + pathFreq.providedPath match { + case Some(provided) => (provided.frequency, Api(provided.vendor, provided.version)) + case None => (0, Api("", "")) + }, + (pathFreq.random, genApi(0)), + (pathFreq.tp2, genApi(2)), + ).map(Method.Post) + + private def genGet(pathFreq: PathFrequencies): Gen[Method.Get] = + Gen.frequency( + pathFreq.providedPath match { + case Some(provided) => (provided.frequency, Api(provided.vendor, provided.version)) + case None => (0, Api("", "")) + }, + (pathFreq.i, Api.GenI), // /i + (pathFreq.ice, Api.GenIce), // /ice.png + (pathFreq.random, genApi(0)), // randomly generated path + (pathFreq.tp1, genApi(1)), // /com.snowplowanalytics.snowplow/tp1 + (pathFreq.tp2, genApi(2)) // /com.snowplowanalytics.snowplow/tp2 + ).map(Method.Get) + + private def genHead(pathFreq: PathFrequencies): Gen[Method.Head] = + Gen.frequency( + pathFreq.providedPath match { + case Some(provided) => (provided.frequency, Api(provided.vendor, provided.version)) + case None => (0, Api("", "")) + }, + (pathFreq.i, Api.GenI), + (pathFreq.ice, Api.GenIce), + (pathFreq.random, genApi(0)), + (pathFreq.tp1, genApi(1)), + (pathFreq.tp2, genApi(2)) + ).map(Method.Head) } def gen( @@ -61,16 +108,19 @@ object HttpRequest { eventPerPayloadMax: Int, now: Instant, frequencies: EventFrequencies, - methodFrequencies: Option[MethodFrequencies] + methodFrequencies: Option[MethodFrequencies], + pathFrequencies: Option[PathFrequencies] ): Gen[HttpRequest] = { - // MethodFrequencies is an option here, at the entrypoint in order not to force a breaking change where this is a lib. + // MethodFrequencies and pathFrequencies are options here, at the entrypoint in order not to force a breaking change where this is a lib. // If it's not provided, we give equal distribution to each to achieve behaviour parity // From here in it's not an option, just to make the code a bit cleaner val methodFreq = methodFrequencies.getOrElse(new MethodFrequencies(1, 1, 1)) + val pathFreq = pathFrequencies.getOrElse(new PathFrequencies(1,1,1,1,1, None)) genWithParts( HttpRequestQuerystring.gen(now, frequencies), HttpRequestBody.gen(eventPerPayloadMin, eventPerPayloadMax, now, frequencies), - methodFreq + methodFreq, + pathFreq ) } @@ -83,25 +133,28 @@ object HttpRequest { eventPerPayloadMax: Int, now: Instant, frequencies: EventFrequencies, - methodFrequencies: Option[MethodFrequencies] + methodFrequencies: Option[MethodFrequencies], + pathFrequencies: Option[PathFrequencies] ): Gen[HttpRequest] = { - // MethodFrequencies is an option here, at the entrypoint in order not to force a breaking change where this is a lib. + // MethodFrequencies and pathFrequencies are options here, at the entrypoint in order not to force a breaking change where this is a lib. // If it's not provided, we give equal distribution to each to achieve behaviour parity // From here in it's not an option, just to make the code a bit cleaner val methodFreq = methodFrequencies.getOrElse(new MethodFrequencies(1, 1, 1)) + val pathFreq = pathFrequencies.getOrElse(new PathFrequencies(1,1,1,1,1, None)) genWithParts( // qs doesn't do duplicates? HttpRequestQuerystring.gen(now, frequencies), HttpRequestBody .genDup(natProb, synProb, natTotal, synTotal, eventPerPayloadMin, eventPerPayloadMax, now, frequencies), - methodFreq + methodFreq, + pathFreq ) } - private def genWithParts(qsGen: Gen[HttpRequestQuerystring], bodyGen: Gen[HttpRequestBody], methodFreq: MethodFrequencies) = + private def genWithParts(qsGen: Gen[HttpRequestQuerystring], bodyGen: Gen[HttpRequestBody], methodFreq: MethodFrequencies, pathFreq: PathFrequencies) = for { - method <- Method.gen(methodFreq) + method <- Method.gen(methodFreq, pathFreq) qs <- Gen.option(qsGen) body <- method match { case Method.Head(_) => Gen.const(None) // HEAD requests can't have a message body diff --git a/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Config.scala b/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Config.scala index a4ddede..b1518ac 100644 --- a/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Config.scala +++ b/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Config.scala @@ -26,7 +26,7 @@ import io.circe.generic.extras.semiauto._ import com.snowplowanalytics.snowplow.eventgen.protocol.event.EventFrequencies import com.snowplowanalytics.snowplow.eventgen.protocol.event.UnstructEventFrequencies -import com.snowplowanalytics.snowplow.eventgen.tracker.HttpRequest.MethodFrequencies +import com.snowplowanalytics.snowplow.eventgen.tracker.HttpRequest.{MethodFrequencies, PathFrequencies, ProvidedPathFrequency} final case class Config( payloadsTotal: Int, @@ -43,6 +43,7 @@ final case class Config( timestamps: Config.Timestamps, eventFrequencies: EventFrequencies, methodFrequencies: Option[MethodFrequencies], + pathFrequencies: Option[PathFrequencies], output: Config.Output ) @@ -94,6 +95,12 @@ object Config { implicit val methodFrequenciesDecoder: Decoder[MethodFrequencies] = deriveConfiguredDecoder [MethodFrequencies] + implicit val providedPathFrequencyDecoder: Decoder[ProvidedPathFrequency] = + deriveConfiguredDecoder [ProvidedPathFrequency] + + implicit val pathFrequenciesDecoder: Decoder[PathFrequencies] = + deriveConfiguredDecoder [PathFrequencies] + implicit val unstructEventFrequenciesDecoder: Decoder[UnstructEventFrequencies] = deriveConfiguredDecoder[UnstructEventFrequencies] diff --git a/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Main.scala b/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Main.scala index 075ff6b..01ce3f4 100644 --- a/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Main.scala +++ b/sinks/src/main/scala/com.snowplowanalytics.snowplow.eventgen/Main.scala @@ -110,7 +110,8 @@ object Main extends IOApp { config.eventPerPayloadMax, time, config.eventFrequencies, - config.methodFrequencies + config.methodFrequencies, + config.pathFrequencies ), rng ) @@ -128,7 +129,7 @@ object Main extends IOApp { ), runGen( HttpRequest - .gen(config.eventPerPayloadMin, config.eventPerPayloadMax, time, config.eventFrequencies, config.methodFrequencies), + .gen(config.eventPerPayloadMin, config.eventPerPayloadMax, time, config.eventFrequencies, config.methodFrequencies, config.pathFrequencies), rng ) )