Skip to content
Merged
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
2 changes: 2 additions & 0 deletions ScenarioMaker/tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,8 @@ def save(self, copy_from_path=None):
continue
if file_extension == ".json":
continue
if filename == "__pycache__":
continue
shutil.copy(os.path.join(copy_from_path, fname), self.working_dir)

# # Delete ScenarioMaker.json in save folder
Expand Down
1 change: 1 addition & 0 deletions core/app_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -3430,6 +3430,7 @@ def process_action(self, action, log_output=False):
param = str(action['name']).strip("[]")
inc_value = float(self._resolve_params_in_item(action['value'], component))
param_section, param_name = self._parse_param_name(param, component)
logging.debug(f"Incrementing parameter pre {param_section}:{param_name} to {inc_value}")
param_value = float(Params.get(param_section, param_name))
new_value = str(param_value + inc_value)
logging.debug(f"Incrementing parameter {param_section}:{param_name} to {new_value}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,15 @@ def run(scenario):
# logging.info("bots_test_server: " + bots_test_server)
# logging.info("==========================================")

#####
# Modification of these limits can result in loss of access to bot server
#####
max_bots = 9
max_duration = 600
# Access key for VERY LIMITED INTERNAL testing of larger meetings. Do NOT use for normal testing. Large meetings from other access keys will be rejected and may result in loss of access to bot server.
if "dRXP2CvR58BXF2" in access_key or "WPYWEbsMfvoIPbX" in access_key:
max_bots = 49
max_duration = 50000
#####
max_duration = 43200

# Validation of test params
if access_key == "-1" and number_of_bots > 0:
logging.error("No valid Teams Bots access key provided! Check that profile contains a Teams Bots Access key, or request one from HOBL Support (HOBLsupport@microsoft.com).")
raise Exception("No Teams Bots Key Provided.")
if number_of_bots > max_bots:
logging.error("Requested bots exceeds the limit of " + str(max_bots) + ". Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")
raise Exception("Requested bots exceeds the limit of " + str(max_bots) + ". Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")
if float(duration) > max_duration:
logging.error("Requested call duration exceeds the limit of " + str(max_duration) + "s. Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")
raise Exception("Requested call duration exceeds the limit of " + str(max_duration) + "s. Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")


if number_of_bots > 0:
request_string = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,15 @@ def run(scenario):
# logging.info("bots_test_server: " + bots_test_server)
# logging.info("==========================================")

#####
# Modification of these limits can result in loss of access to bot server
#####
max_bots = 9
max_duration = 600
# Access key for VERY LIMITED INTERNAL testing of larger meetings. Do NOT use for normal testing. Large meetings from other access keys will be rejected and may result in loss of access to bot server.
if "dRXP2CvR58BXF2" in access_key or "WPYWEbsMfvoIPbX" in access_key:
max_bots = 49
max_duration = 50000
#####
max_duration = 43200

# Validation of test params
if access_key == "-1" and number_of_bots > 0:
logging.error("No valid Teams Bots access key provided! Check that profile contains a Teams Bots Access key, or request one from HOBL Support (HOBLsupport@microsoft.com).")
raise Exception("No Teams Bots Key Provided.")
if number_of_bots > max_bots:
logging.error("Requested bots exceeds the limit of " + str(max_bots) + ". Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")
raise Exception("Requested bots exceeds the limit of " + str(max_bots) + ". Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")
if float(duration) > max_duration:
logging.error("Requested call duration exceeds the limit of " + str(max_duration) + "s. Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")
raise Exception("Requested call duration exceeds the limit of " + str(max_duration) + "s. Attempting to exceed these limits can result in permanent loss of access to bot services for your organization.")


if number_of_bots > 0:
request_string = ""
Expand Down
1 change: 1 addition & 0 deletions scenarios/windows/_library/procyon/procyon_run/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .procyon_run import *
86 changes: 86 additions & 0 deletions scenarios/windows/_library/procyon/procyon_run/code_1LC4L8F.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.

import logging
import csv
import os
import time
import math
import xml.etree.ElementTree as ET
from core.parameters import Params

def run(scenario):
logging.debug('Executing code block: code_1LC4L8F.py')
test_def = Params.get('procyon_run', 'test_definition')
loop_num = Params.get('procyon_run', 'loop_number')
if loop_num is None or loop_num == "":
loop_num = "1"
loop_num = int(float(loop_num))
if ".def" not in test_def:
test_def = test_def + ".def"
logging.info(f"Running Procyon loop {loop_num} using test definition: {test_def}")
scenario._remote_make_dir(f"{scenario.dut_data_path}\\procyon", delete=False)
scenario._call(["C:\\Program Files\\UL\\Procyon\\ProcyonCmd.exe", f"--definition={test_def} --loop=1 --export-xml=c:\\hobl_data\\procyon\\Results_{loop_num}.xml"], fail_on_exception=False)

# Record timestamp
t = time.time() - scenario.scenario_start_time
# round up t to the nearest second
timestamp = math.ceil(t)

# Download xml result file back to host
scenario._copy_data_from_remote(scenario.result_dir + "\\procyon", source=f"{scenario.dut_data_path}\\procyon")

# Read scores from xml file and write to csv
xml_file = scenario.result_dir + f"\\procyon\\Results_{loop_num}.xml"
tree = ET.parse(xml_file)
root = tree.getroot()

score_headers = []
values = {}
for element in root.iter():
if "Score" in element.tag:
if element.tag not in values:
score_headers.append(element.tag)
values[element.tag] = (element.text or "").strip()

if not score_headers:
logging.warning(f"No score tags found in XML file: {xml_file}")
return

headers = ["timestamp"] + score_headers

csv_file = scenario.result_dir + f"\\procyon\\Procyon_Results.trace"

csv_exists = os.path.exists(csv_file)
with open(csv_file, "a", newline="", encoding="utf-8") as file_handle:
writer = csv.writer(file_handle)
if not csv_exists:
writer.writerow(headers)
writer.writerow([timestamp] + [values.get(h, "") for h in score_headers])

# Read Procyon_results.trace file, average the scores for each column, and output into a summary csv as key,val pairs
summary_file = scenario.result_dir + f"\\Procyon_Summary.csv"
with open(csv_file, "r", newline="", encoding="utf-8") as file_handle:
reader = csv.DictReader(file_handle)
summary_data = {}
count = 0
for row in reader:
count += 1
for key, value in row.items():
if key == "timestamp":
continue
try:
value = float(value)
except ValueError:
value = 0.0
if key not in summary_data:
summary_data[key] = 0.0
summary_data[key] += value
if count > 0:
for key in summary_data:
summary_data[key] /= count

with open(summary_file, "w", newline="", encoding="utf-8") as file_handle:
writer = csv.writer(file_handle)
for key, value in summary_data.items():
writer.writerow([f"Procyon-{key}", value])
14 changes: 14 additions & 0 deletions scenarios/windows/_library/procyon/procyon_run/default_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.

from core.parameters import Params
from utilities.open_source.modules import import_run_user_only

def run():
Params.setCalculated('scenario_section', __package__.split('.')[-1])
run_user_only()
Params.setDefault('procyon_run', 'test_definition', 'office_productivity', desc='Workload to run', valOptions=['office_productivity', 'essentials', 'video_playback_batterylife', 'photo_editing', 'video_editing', 'ai_computer_vision_winml', 'ai_computer_vision_snpe', 'ai_computer_vision_openvino', 'ai_computer_vision_tensorrt', 'ai_computer_vision_ryzenai'])
return

def run_user_only():
return
34 changes: 34 additions & 0 deletions scenarios/windows/_library/procyon/procyon_run/procyon_run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"author": "jewilder",
"capture_device": "",
"children": [],
"create_date": "2026-05-06",
"description": "Run Procyon",
"enabled": true,
"id": "1LC3YWY",
"type": "Information"
},
{
"children": [],
"delay": "0.0",
"description": "Workload to run",
"enabled": true,
"id": "1LC437A",
"name": "[test_definition]",
"type": "Set Default",
"val_options": "office_productivity,essentials,video_playback_batterylife,photo_editing,video_editing,ai_computer_vision_winml,ai_computer_vision_snpe,ai_computer_vision_openvino,ai_computer_vision_tensorrt,ai_computer_vision_ryzenai",
"value": "office_productivity"
},
{
"children": [],
"delay": "0.0",
"description": "Run Procyon",
"enabled": true,
"file_name": [
"code_1LC4L8F.py"
],
"id": "1LC4L8F",
"type": "Code"
}
]
59 changes: 59 additions & 0 deletions scenarios/windows/_library/procyon/procyon_run/procyon_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.

import core.app_scenario
from core.parameters import Params
import logging
import os
from . import default_params

# Description:
# Automatically generated standard scenario.

class ProcyonRun(core.app_scenario.Scenario):
# Set default parameters:
default_params.run()

actions = None

def setUp(self):
# Load actions JSON.
actions_json = os.path.join(os.path.dirname(__file__), "procyon_run.json")
self.actions = self.load_action_json(actions_json)

# Execute Setup actions, if they exist
setup_action = self._find_next_type("Setup", json=self.actions)
if setup_action is not None:
self.run_actions(setup_action["children"])

# Call base class setUp() to dump config, call tool callbacks, and start measurment
core.app_scenario.Scenario.setUp(self)


def runTest(self):
# Execute Run Test actions, if they exist
runtest_action = self._find_next_type("Run Test", json=self.actions)
if runtest_action is not None:
self.run_actions(runtest_action["children"])
return

# If no "Run Test", "Setup", or "Teardown" specified, then just execute the whole list
setup_action = self._find_next_type("Setup", json=self.actions)
teardown_action = self._find_next_type("Teardown", json=self.actions)
if runtest_action is None and setup_action is None and teardown_action is None:
self.run_actions(self.actions)


def tearDown(self):
# Call base class tearDown() to stop measurment, copy back data from DUT, and call tool callbacks
core.app_scenario.Scenario.tearDown(self)

# Execute Teardown actions, if they exist
teardown_action = self._find_next_type("Teardown", json=self.actions)
if teardown_action is not None:
self.run_actions(teardown_action["children"])


def kill(self):
# In case of scenario failure or termination, kill any applications left open here:
return
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .procyon_setup import *
39 changes: 39 additions & 0 deletions scenarios/windows/_library/procyon/procyon_setup/code_1LCYJ1C.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import logging
import time
from core.parameters import Params

def run(scenario):
logging.debug('Executing code block: code_1L9EY7T.py')
logging.info("Preparing Procyon for first use.")

# Get and verify parameters
host_path = Params.get('procyon_setup', 'host_path')
if host_path == "" or host_path is None:
logging.warning("Procyon host_path is not set, not installing.")
return
license_key = Params.get('procyon_setup', 'key')
if license_key == "":
logging.error("Procyon license key is not set.")
scenario.fail("Procyon license key is not set.")

# Check if already installed
if not scenario.checkPrepStatusNew([("procyon", [host_path])]):
logging.info("Procyon already installed.")
return

# Upload installer
target = f"{scenario.dut_exec_path}\\procyon_setup"
logging.info(f"Uploading Procyon setup files to {target}")
scenario._upload(host_path + "\\*", target, check_modified=False)

# Run installer
logging.info("Running Procyon setup script.")
scenario._call([f"{target}\\procyon-setup.exe", "/s /sms"], expected_exit_code="0")

# Register license
logging.info("Registering license.")
scenario._call(["C:\\Program Files\\UL\\Procyon\\ProcyonCmd.exe", f"--register={license_key}"])

# Create prep status file to indicate completion
scenario.createPrepStatusControlFile(suffix=[host_path], module="procyon")
scenario._sleep_to_now()
15 changes: 15 additions & 0 deletions scenarios/windows/_library/procyon/procyon_setup/default_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.

from core.parameters import Params
from utilities.open_source.modules import import_run_user_only

def run():
Params.setCalculated('scenario_section', __package__.split('.')[-1])
run_user_only()
Params.setDefault('procyon_setup', 'host_path', '', desc='Path on host to Procyon setup files', valOptions=[])
Params.setDefault('procyon_setup', 'key', '', desc='Procyon license key', valOptions=[])
return

def run_user_only():
return
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[
{
"author": "jewilder",
"capture_device": "",
"children": [],
"create_date": "2026-05-07",
"description": "Setup Procyon",
"enabled": true,
"id": "1LCYHU8",
"type": "Information"
},
{
"children": [],
"delay": "0.0",
"description": "Path on host to Procyon setup files",
"enabled": true,
"id": "1LE65M3",
"name": "[host_path]",
"type": "Set Default",
"val_options": "",
"value": ""
},
{
"children": [],
"delay": "0.0",
"description": "Procyon license key",
"enabled": true,
"id": "1LE6615",
"name": "[key]",
"type": "Set Default",
"val_options": "",
"value": ""
},
{
"children": [],
"delay": "0.0",
"description": "Install Procyon",
"enabled": true,
"file_name": [
"code_1LCYJ1C.py"
],
"id": "1LCYJ1C",
"type": "Code"
}
]
Loading