Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ before_install:
- pip install 'urllib3<2.0' #Enforce SSL version to be able to run twine
- pip install -U build
- pip install -U twine
- curl -fsSL https://ollama.com/install.sh | sh
install:
- pip install -r requirements.txt
- pip install .
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ dependencies = [
"flask",
"waitress",
"tqdm",
"haralyzer"
"haralyzer",
"ollama",
"pandas",
"Markdown",
]

[project.scripts]
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ waitress # Production WSGI server
flask # WSGI server for development the code
tqdm
haralyzer
pandas
ollama
pandas
Markdown
Empty file added src/__init__.py
Empty file.
64 changes: 64 additions & 0 deletions src/javacore_analyser/ai/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#
# Copyright IBM Corp. 2025 - 2025
# SPDX-License-Identifier: Apache-2.0
#

import logging
import markdown
from ollama import chat
from ollama import ChatResponse
import ollama

from javacore_analyser.constants import DEFAULT_LLM_MODEL



# prerequisites:
# install Ollama from https://ollama.com/download

class Ai:
"""
A class representing an AI model infuser.

Attributes:
prompt (str): The current prompt being infused.
javacore_set (set): A set of Java cores for the AI model.
model (str): The AI model to be used for inference.
"""

def __init__(self, javacore_set):
self.prompt = ""
self.javacore_set = javacore_set
self.model = DEFAULT_LLM_MODEL
logging.info("Pulling model: " + self.model)
ollama.pull(self.model)
logging.info("Model pulled: " + self.model)


def set_model(self, model):
self.model = model


def infuse(self, prompter):
content = ""
self.prompt = prompter.construct_prompt()
if self.prompt and len(self.prompt) > 0:
logging.debug("Infusing prompt: " + self.prompt)
response: ChatResponse = chat(model=self.model, messages=[
{
'role': 'user',
'content': self.prompt,
},
])
logging.debug("Infused finished")
content = response.message.content
return content

def response_to_html(self, response):
html = markdown.markdown(response)
return html

def infuse_in_html(self, prompter):
content = self.infuse(prompter)
return self.response_to_html(content)

36 changes: 36 additions & 0 deletions src/javacore_analyser/ai/ai_overview_prompter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#
# Copyright IBM Corp. 2025 - 2025
# SPDX-License-Identifier: Apache-2.0
#

from javacore_analyser.ai.prompter import Prompter


# Assisted by watsonx Code Assistant

class AiOverviewPrompter(Prompter):
"""
AiOverviewPrompter class generates a detailed prompt for explaining overview section.

Attributes:
- javacore_set (JavacoreSet): An object containing Java configuration details.
"""

def construct_prompt(self):
prompt = f'''
Given the information below, explain how to improve the performance of the java application
Java configuration:
Number of CPUs: {self.javacore_set.number_of_cpus}
Xmx={self.javacore_set.xmx}
Xms={self.javacore_set.xms}
Xmn={self.javacore_set.xmn}
GC policy: {self.javacore_set.gc_policy}
Compressed references: {self.javacore_set.compressed_refs}
Verbose GC: {self.javacore_set.verbose_gc}
OS level: {self.javacore_set.os_level}
System architecture: {self.javacore_set.architecture}
Java version: {self.javacore_set.java_version}
Command line: {self.javacore_set.cmd_line}
Limit number of suggestions to 5 most siginificant.
'''
return prompt
13 changes: 13 additions & 0 deletions src/javacore_analyser/ai/prompter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# Copyright IBM Corp. 2025 - 2025
# SPDX-License-Identifier: Apache-2.0
#

class Prompter:

def __init__(self, javacore_set):
self.javacore_set = javacore_set


def construct_prompt(self):
return ""
18 changes: 18 additions & 0 deletions src/javacore_analyser/ai/tips_prompter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Copyright IBM Corp. 2025 - 2025
# SPDX-License-Identifier: Apache-2.0
#

from javacore_analyser.ai.prompter import Prompter


class TipsPrompter(Prompter) :

def construct_prompt(self):
prompt = ""
if len(self.javacore_set.tips) > 0:
prompt = "Analyse the tips to help identify performance bottlenecks in a Java application: \n"
for tip in self.javacore_set.tips:
prompt += tip + "\n"
prompt += "Provide maximum of 5 suggestions for performance improvements."
return prompt
2 changes: 2 additions & 0 deletions src/javacore_analyser/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@
DEFAULT_REPORTS_DIR = "reports"
DEFAULT_PORT = 5000
TEMP_DIR = "temp_data" # Folder to store temporary data for creating reports

DEFAULT_LLM_MODEL = "ibm/granite4:latest" # https://ollama.com/ibm/granite4
25 changes: 18 additions & 7 deletions src/javacore_analyser/data/xml/report.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@
href="javascript:expand_it(system_properties, toggle_system_properties)"
class="expandit">System Information</a></h3>
<div id="system_properties" style="display:none;">
<xsl:if test="doc/system_info/@ai_overview != ''">
<h4>AI Overview:</h4>
<xsl:value-of select="doc/system_info/@ai_overview" disable-output-escaping="yes"/>
</xsl:if>
<h4>Basic JVM Configuration</h4>
<table id="sys_info_table" class="tablesorter">
<thead>
Expand Down Expand Up @@ -407,15 +411,22 @@
<h3><a id="toggleintelligenttips" href="javascript:expand_it(intelligenttips,toggleintelligenttips)" class="expandit">Intelligent tips</a></h3>
<div id="intelligenttips" style="display:none;">
<xsl:choose>
<xsl:when test="doc/report_info/tips/tip">
<ul>
<xsl:for-each select="doc/report_info/tips/tip">
<li><xsl:value-of select="current()"/></li>
</xsl:for-each>
</ul>
<xsl:when test="doc/report_info/tips/@ai_tips != ''">
<xsl:value-of select="doc/report_info/tips/@ai_tips" disable-output-escaping="yes" />
</xsl:when>
<xsl:otherwise>
We did not find any tips for you.
<xsl:choose>
<xsl:when test="doc/report_info/tips/tip">
<ul>
<xsl:for-each select="doc/report_info/tips/tip">
<li><xsl:value-of select="current()"/></li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
We did not find any tips for you.
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/javacore_analyser/javacore_analyser_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,13 @@ def main():
default=DEFAULT_FILE_DELIMITER)
parser.add_argument("--skip_boring", help='Skips drilldown page generation for threads that do not do anything',
default='True')
parser.add_argument("--ai", default='False', required=False, help="Use AI genereated analysis")
args = parser.parse_args()

input_param = args.input
output_param = args.output
files_separator = args.separator
Properties.get_instance().ai = args.ai
Properties.get_instance().skip_boring = args.skip_boring != 'False'

batch_process(input_param, output_param, files_separator)
Expand Down
11 changes: 9 additions & 2 deletions src/javacore_analyser/javacore_analyser_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from pathlib import Path

from flask import Flask, render_template, request, send_from_directory, redirect
from javacore_analyser.properties import Properties
from waitress import serve

import javacore_analyser.javacore_analyser_batch
Expand Down Expand Up @@ -139,22 +140,28 @@ def main():
parser.add_argument("--port", help="Port to run application", default=DEFAULT_PORT)
parser.add_argument("--reports-dir", help="Directory where app reports are stored",
default=DEFAULT_REPORTS_DIR)
parser.add_argument("--ai", help="Use AI to analyse the data", default=False)
parser.add_argument("--skip_boring", help='Skips drilldown page generation for threads that do not do anything', default=False)
args = parser.parse_args()
debug = args.debug
port = args.port
reports_directory = args.reports_dir
ai = args.ai
Properties.get_instance().ai = args.ai
Properties.get_instance().skip_boring = args.skip_boring != 'False'

run_web(debug, port, reports_directory)
run_web(debug, port, reports_directory, ai)


def run_web(debug=False, port=5000, reports_directory=DEFAULT_REPORTS_DIR):
def run_web(debug=False, port=5000, reports_directory=DEFAULT_REPORTS_DIR, ai=False):
global reports_dir
reports_dir = os.path.abspath(reports_directory)
create_console_logging()
logging.info("Javacore analyser")
logging.info("Python version: " + sys.version)
logging.info("Preferred encoding: " + locale.getpreferredencoding())
logging.info("Reports directory: " + reports_dir)
logging.info("AI: " + str(ai))
create_file_logging(reports_dir)
create_temp_data_in_reports_dir(reports_dir)
if debug:
Expand Down
16 changes: 16 additions & 0 deletions src/javacore_analyser/javacore_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
from javacore_analyser.snapshot_collection_collection import SnapshotCollectionCollection
from javacore_analyser.verbose_gc import VerboseGcParser

from javacore_analyser.ai.ai import Ai
from javacore_analyser.ai.ai_overview_prompter import AiOverviewPrompter
from javacore_analyser.ai.tips_prompter import TipsPrompter


def _create_xml_xsl_for_collection(tmp_dir, templates_dir, xml_xsl_filename, collection, output_file_prefix):
logging.info("Creating xmls and xsls in " + tmp_dir)
Expand Down Expand Up @@ -84,6 +88,9 @@ def __init__(self, path):
self.stacks = SnapshotCollectionCollection(CodeSnapshotCollection)
self.report_xml_file = None

self.ai_overview = ""
self.ai_tips = ""

self.doc = None

'''
Expand Down Expand Up @@ -117,6 +124,8 @@ def process_javacores(input_path):
jset.print_blockers()
jset.print_thread_states()
jset.generate_tips()
if Properties.get_instance().ai.lower() == 'true':
jset.add_ai()
return jset

# Assisted by WCA@IBM
Expand Down Expand Up @@ -439,8 +448,10 @@ def __create_report_xml(self, output_file):
tip_node = self.doc.createElement("tip")
tips_node.appendChild(tip_node)
tip_node.appendChild(self.doc.createTextNode(tip))
tips_node.setAttribute("ai_tips", self.ai_tips)

user_args_list_node = self.doc.createElement("user_args_list")
system_info_node.setAttribute("ai_overview", self.ai_overview)
system_info_node.appendChild(user_args_list_node)
for arg in self.user_args:
arg_node = self.doc.createElement("user_arg")
Expand Down Expand Up @@ -724,3 +735,8 @@ def generate_tips(self):
for tip in tips.TIPS_LIST:
tip_class = getattr(tips, tip)
self.tips.extend(tip_class.generate(self))

def add_ai(self):
ai = Ai(self)
self.ai_overview = ai.infuse_in_html(AiOverviewPrompter(self))
self.ai_tips = ai.infuse_in_html(TipsPrompter(self))
5 changes: 5 additions & 0 deletions src/javacore_analyser/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
# SPDX-License-Identifier: Apache-2.0
#

# Assisted by watsonx Code Assistant

class Properties:
"""
A singleton class to manage properties that should not be changed after initialization.
"""

__instance = None

Expand Down
4 changes: 4 additions & 0 deletions test/test_javacore_analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def setUpClass(self):
self.expateerror = ["javacore_analyser", "test/data/verboseGcJavacores", "tmp"]
self.threadnameswithquotes = ["javacore_analyser", "test/data/quotationMarks", "tmp"]
self.encoding = ["javacore_analyser", "test/data/encoding/javacore.20220606.114458.32888.0001.txt", "tmp"]
self.ai = ["javacore_analyser", "test/data/archives/javacores.7z", "tmp", "--ai=true"]
rm_tmp_dir()

def test_api(self):
Expand Down Expand Up @@ -97,6 +98,9 @@ def test_run_two_javacores(self):
def test_run_two_javacores_custom_separator(self):
self.runMainWithParams(self.twofilesargs_different_separator)

def test_run_ai(self):
self.runMainWithParams(self.ai)

def test_error_for_archive_without_javacores(self):
# Run with params from https://stackoverflow.com/questions/18668947/how-do-i-set-sys-argv-so-i-can-unit-test-it
# Redirect console from https://stackoverflow.com/questions/33767627/python-write-unittest-for-console-print
Expand Down