Last active
April 2, 2023 10:13
-
-
Save dacr/69d06001b2ac0be5f8e0ce17c5a6e357 to your computer and use it in GitHub Desktop.
Drools web based user interactions with a performance diagnostic knowledge base / published by https://github.com/dacr/code-examples-manager #50d2f501-e740-42de-984b-8ad50001137b/c7c1ea2205c96c21b8199ebc6687fb6b7cedf80d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// summary : Drools web based user interactions with a performance diagnostic knowledge base | |
// keywords : scala, drools, mvel, scalatest, ai, knowledgebase, interactions, akka-http | |
// publish : gist | |
// authors : David Crosson | |
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2) | |
// id : 50d2f501-e740-42de-984b-8ad50001137b | |
// execution : scala ammonite script (http://ammonite.io/) - run as follow 'amm scriptname.sc' | |
// created-on : 2018-09-12T21:11:39+02:00 | |
// managed-by : https://github.com/dacr/code-examples-manager | |
interp.resolutionHooks += { fetch => | |
// -- This is mandatory with drools >= 7.0.46 because drools sources artifacts also brings kie.conf | |
// -- (it generates resources conflict at KIE init) and because by default ammonite also load sources artifact... | |
// -- WARN : YOU MAY HAVE TO CLEANUP YOUR LOCAL COURSIER CACHE ~/.cache/coursier to remove sources artifact | |
// -- WARN : find ~/.cache/coursier/ -name "*drools*-sources.jar" | xargs rm -f | |
import scala.jdk.CollectionConverters._ | |
fetch.withClassifiers(fetch.getClassifiers.asScala.filter(_ != "sources").asJava) | |
} | |
@ | |
import $ivy.`fr.janalyse::drools-scripting:1.0.15` | |
import fr.janalyse.droolscripting._ | |
import $ivy.`com.typesafe.akka::akka-http:10.2.4` | |
import $ivy.`com.typesafe.akka::akka-stream:2.6.13` | |
import scala.collection.immutable.Queue | |
case class Question(reference:String, message:String) { | |
val possibleAnswersRE = """(\[[^]]+\])""".r | |
def possibleAnswers():List[String] = { | |
possibleAnswersRE.findAllMatchIn(message).map(_.group(1)).toList | |
} | |
} | |
class DroolsInternalHelper(engine:DroolsEngine) { | |
private var questions:Queue[Question] = Queue.empty | |
def current:Option[Question] = questions.headOption | |
def ask(reference:String, question:String):Unit = { | |
questions = questions.enqueue(Question(reference, question)) | |
} | |
def respond(reference:String, answer:String):Unit = { | |
if (!questions.isEmpty) { | |
questions = questions.tail | |
val json = s"""{"reference":"$reference", "answer":"$answer"}""" | |
engine.insertJson(json, "interact.UserResponse") | |
} | |
} | |
def reset():Unit = { | |
questions = Queue.empty | |
} | |
} | |
class PerformanceExpertSystem { | |
val helperClassName = classOf[DroolsInternalHelper].getCanonicalName | |
val drl = | |
s"""package interact | |
|dialect "mvel" | |
| | |
|global org.slf4j.Logger logger | |
|global $helperClassName helper | |
| | |
|declare PerformanceIssue | |
| description: String | |
|end | |
| | |
|declare UserResponse | |
| reference: String | |
| answer: String | |
|end | |
| | |
|// ------------------------------------------------------------------------------- | |
|rule "perf-issue" when then insert("reset"); end | |
| | |
|rule "perf-issue after reset" when str:String(this == "reset") | |
|then | |
| helper.ask("perf-issue", "Do you encounter a performance problem ? [yes] [no]"); | |
| delete(str); | |
|end | |
|// ------------------------------------------------------------------------------- | |
|rule "perf-context" when | |
| UserResponse(reference == "perf-issue", answer == "yes") | |
|then | |
| helper.ask("perf-context", "What is the nature of your performance issue ? [slow response time] [random crash] [high cpu]") | |
|end | |
|// ------------------------------------------------------------------------------- | |
|rule "os-context" when | |
| UserResponse(reference == "perf-issue", answer == "yes") | |
|then | |
| helper.ask("os-context", "What is your operating system ? [Linux] [Windows] [MacOS]") | |
|end | |
|// ------------------------------------------------------------------------------- | |
|rule "app-context" when | |
| UserResponse(reference == "perf-issue", answer == "yes") | |
|then | |
| helper.ask("app-context", "What is your technical context ? [java] [php] [javascript]") | |
|end | |
|// ------------------------------------------------------------------------------- | |
|//rule "java-context" when | |
|// UserResponse(reference == "app-context", answer == "java") | |
|//then | |
|// helper.ask("java-vendor", "What is your java vendor ? [oracle] [openjdk] [ibm]") | |
|// helper.ask("java-release", "What is your java release ? <text>") | |
|//end | |
|// ------------------------------------------------------------------------------- | |
|rule "javascript troll rule" when | |
| UserResponse(reference == "app-context", answer=="javascript") | |
|then | |
| insert(new PerformanceIssue("Oh NO you're using javascript !! do you know it means java scripting ?")); | |
|end | |
|// ------------------------------------------------------------------------------- | |
|rule "php troll rule" when | |
| UserResponse(reference == "app-context", answer=="php") | |
|then | |
| insert(new PerformanceIssue("Seriously PHP !?!? A 20th century language...")); | |
|end | |
|// ------------------------------------------------------------------------------- | |
|rule "java troll rule" when | |
| UserResponse(reference == "app-context", answer=="java") | |
|then | |
| insert(new PerformanceIssue("Java ? And did you consider cobol ?")); | |
|end | |
|// ------------------------------------------------------------------------------- | |
|rule "windows/macos troll rule" when | |
| UserResponse(reference == "os-context", answer in ("Windows","MacOS") ) | |
|then | |
| insert(new PerformanceIssue("What ! Using desktop Operating System to power your backend ?")); | |
|end | |
|""".stripMargin | |
val engineConfig = DroolsEngineConfig(equalsWithIdentity=false, withDroolsLogging=false) | |
val engine = DroolsEngine(drl, engineConfig) | |
val helper = new DroolsInternalHelper(engine) | |
engine.session.setGlobal("helper", helper) | |
def session = engine.session | |
def run() { | |
println("Ctrl-C to stop execution") | |
engine.session.fireUntilHalt() | |
} | |
} | |
class AIService(expertSystem:PerformanceExpertSystem) { | |
import akka.http.scaladsl._ | |
import akka.http.scaladsl.server.Directives._ | |
import akka.http.scaladsl.model._ | |
import scala.concurrent.Await | |
import scala.concurrent.duration.Duration | |
import akka.http.scaladsl.model.ContentTypes._ | |
implicit val system = akka.actor.ActorSystem("MySystem") | |
implicit val materializer = akka.stream.ActorMaterializer() | |
implicit val executionContext = system.dispatcher | |
def render(question:Question): HttpResponse = { | |
val renderedQuestion = | |
question.possibleAnswers().foldLeft(question.message) { case (nquest,answer) => | |
val cleanedAnswer = answer.tail.init // remove [] | |
val lnk = s"/response/${question.reference}/$cleanedAnswer" | |
nquest.replace(answer, s"""<a href="$lnk">$answer</a> """) | |
} | |
val content = | |
s"""<html> | |
| <body> | |
| <h1>$renderedQuestion</h1> | |
| </body> | |
|</html>""".stripMargin | |
HttpResponse(entity = HttpEntity(`text/html(UTF-8)`, content)) | |
} | |
def renderDiagnostic():HttpResponse = { | |
val issues = expertSystem.engine.getModelInstances("interact.PerformanceIssue") | |
val renderedIssues = | |
if (issues.isEmpty) { | |
"No issue found" | |
} else { | |
issues.map(issue => s"<p>$issue</p>").mkString("\n") | |
} | |
val content = | |
s"""<html> | |
| <body> | |
| <h1>Performance diagnostics results</h1> | |
| $renderedIssues | |
| <br/> | |
| <a href="/reset">Reset</a> | |
| </body> | |
|</html>""".stripMargin | |
HttpResponse(entity = HttpEntity(`text/html(UTF-8)`, content)) | |
} | |
def stopRoute = path("shutdown") { | |
get { complete { expertSystem.engine.session.halt(); shutdown() ; "OK" } } | |
} | |
def homeRoute= path("") { | |
get { | |
complete { | |
expertSystem.helper.current match { | |
case Some(question) => render(question) | |
case None => renderDiagnostic() | |
} | |
} | |
} | |
} | |
def responseRoute = path("response" / Segment / Segment) { | |
case (reference, answer) => get { | |
expertSystem.helper.respond(reference, answer) | |
redirect("/", StatusCodes.TemporaryRedirect) | |
} | |
} | |
def resetRoute = path("reset") { | |
get { | |
for { ob <- expertSystem.engine.getObjects } { | |
val handle = expertSystem.engine.getFactHandle(ob) | |
expertSystem.session.delete(handle) | |
} | |
expertSystem.engine.insert("reset") | |
expertSystem.helper.reset() | |
redirect("/", StatusCodes.TemporaryRedirect) | |
} | |
} | |
val chosenPort = 6080 | |
val chosenInterface = "0.0.0.0" | |
val routes = stopRoute ~ resetRoute ~ homeRoute ~ responseRoute | |
val bindingFuture = Http().bindAndHandle(routes, chosenInterface, chosenPort) | |
println(s"***** Local web server is running on $chosenInterface:$chosenPort *****") | |
def shutdown():Unit = bindingFuture.flatMap(_.unbind()).onComplete { _ => system.terminate() } | |
def waitForShutdown(): Unit = Await.ready(system.whenTerminated, Duration.Inf) | |
} | |
val expertSystem = new PerformanceExpertSystem | |
val aiService = new AIService(expertSystem) | |
expertSystem.run() | |
aiService.waitForShutdown() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment