diff --git a/.last_state.json b/.last_state.json index b1ac5e8..96509f7 100644 --- a/.last_state.json +++ b/.last_state.json @@ -1,4 +1,4 @@ { - "template": "F:\\PyPro\\PCM_Report\\configs\\600泵\\template.docx", + "template": "C:\\PPRO\\PCM_Report\\configs\\600泵\\template.docx", "category_name": "600泵" } \ No newline at end of file diff --git a/add_save_status_columns.py b/add_save_status_columns.py new file mode 100644 index 0000000..708cf50 --- /dev/null +++ b/add_save_status_columns.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +添加保存状态字段到 experiments 表 +用于跟踪实验数据自动保存的状态 +""" +import sqlite3 +from pathlib import Path +from logger import get_logger + +logger = get_logger() + + +def add_save_status_columns(): + """添加 save_status 和 save_error 字段""" + try: + db_path = Path(__file__).parent / "experiments.db" + + if not db_path.exists(): + logger.error(f"数据库文件不存在: {db_path}") + return False + + db = sqlite3.connect(str(db_path)) + cur = db.cursor() + + # 检查字段是否已存在 + cur.execute("PRAGMA table_info(experiments)") + columns = [row[1] for row in cur.fetchall()] + + logger.info(f"当前 experiments 表字段: {columns}") + + # 添加 save_status 字段 + if 'save_status' not in columns: + logger.info("添加 save_status 字段...") + cur.execute(""" + ALTER TABLE experiments + ADD COLUMN save_status TEXT DEFAULT NULL + """) + logger.info("✅ save_status 字段添加成功") + else: + logger.info("save_status 字段已存在,跳过") + + # 添加 save_error 字段 + if 'save_error' not in columns: + logger.info("添加 save_error 字段...") + cur.execute(""" + ALTER TABLE experiments + ADD COLUMN save_error TEXT DEFAULT NULL + """) + logger.info("✅ save_error 字段添加成功") + else: + logger.info("save_error 字段已存在,跳过") + + db.commit() + + # 验证字段已添加 + cur.execute("PRAGMA table_info(experiments)") + columns_after = [row[1] for row in cur.fetchall()] + logger.info(f"更新后 experiments 表字段: {columns_after}") + + db.close() + + logger.info("✅ 数据库迁移完成") + return True + + except Exception as e: + logger.error(f"❌ 添加字段失败: {e}", exc_info=True) + return False + + +if __name__ == "__main__": + print("开始添加保存状态字段...") + success = add_save_status_columns() + + if success: + print("✅ 数据库迁移成功完成") + else: + print("❌ 数据库迁移失败,请查看日志") diff --git a/configs/1000泵/config.json b/configs/1000泵/config.json index a3f1b26..852db14 100644 --- a/configs/1000泵/config.json +++ b/configs/1000泵/config.json @@ -2,7 +2,7 @@ "influx": { "url": "http://10.0.5.232:8086", "org": "MEASCON", - "token": "_jtoxcVDIbol2Uqt_vlhidut-EO0Xo0ZXea2UC5a5Bgotk836F0xPN4NSGY1jYI_WaBKRau4RyZ-g2XSFiNdXw==", + "token": "JPZMq2UP5ORhLq8CfsPbawl6k0MSDlJmEwMJ2uvR_TXqW5bUOWIYBQOSXkGNzDqOU3rnuGpIxGxrB_mlAF-EEw==", "username": "PCM", "password": "1842moon", "landingUrl": "http://10.0.5.232:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h", @@ -13,106 +13,223 @@ "chart1": { "type": "chart", "label": "chart1", - "title": "", + "title": "chart1", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart2": { "type": "chart", "label": "chart2", - "title": "", + "title": "chart2", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart3": { "type": "chart", "label": "chart3", - "title": "", + "title": "chart3", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart4": { "type": "chart", "label": "chart4", - "title": "", + "title": "chart4", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart5": { "type": "chart", "label": "chart5", - "title": "", + "title": "chart5", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart6": { "type": "chart", "label": "chart6", - "title": "", + "title": "chart6", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart7": { "type": "chart", "label": "chart7", - "title": "", + "title": "chart7", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart8": { "type": "chart", "label": "chart8", - "title": "", + "title": "chart8", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart9": { "type": "chart", "label": "chart9", - "title": "", + "title": "chart9", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart10": { "type": "chart", "label": "chart10", - "title": "", + "title": "chart10", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart11": { "type": "chart", "label": "chart11", - "title": "", + "title": "chart11", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "chart12": { "type": "chart", "label": "chart12", - "title": "", + "title": "chart12", "value": "", "dbQuery": "", - "chart": {} + "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + } }, "table1": { "type": "table", "label": "table1", - "title": "", + "title": "table1", "value": "", "dbQuery": "", "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + }, "table": { "firstColumn": "time", "firstTitle": "", @@ -122,10 +239,19 @@ "table2": { "type": "table", "label": "table2", - "title": "", + "title": "table2", "value": "", "dbQuery": "", "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + }, "table": { "firstColumn": "time", "firstTitle": "", @@ -135,10 +261,19 @@ "table3": { "type": "table", "label": "table3", - "title": "", + "title": "table3", "value": "", "dbQuery": "", "chart": {}, + "influx": { + "bucket": "", + "measurement": "", + "fields": [], + "filters": {}, + "timeRange": "-1h", + "aggregate": "", + "windowPeriod": "" + }, "table": { "firstColumn": "time", "firstTitle": "", diff --git a/configs/1000泵/dashboard.json b/configs/1000泵/dashboard.json new file mode 100644 index 0000000..6ec4c8a --- /dev/null +++ b/configs/1000泵/dashboard.json @@ -0,0 +1,63 @@ +{ + "canvas": { + "x": 0.0, + "y": 0.0, + "w": 1920.0, + "h": 1080.0 + }, + "widgets": [ + { + "widget_type": "label", + "x": 173.0, + "y": 210.0, + "w": 162.0, + "h": 62.0, + "z": 1.0, + "config": { + "fieldName": "主轴承#3", + "prefix": "主轴承温度", + "suffix": " ℃", + "fontSize": 16, + "color": "#FFFFFF" + } + }, + { + "widget_type": "label", + "x": 46.0, + "y": 378.0, + "w": 162.0, + "h": 62.0, + "z": 1.0, + "config": { + "fieldName": "减速箱小轴承2", + "prefix": "减速箱小轴承温度", + "suffix": "℃", + "fontSize": 16, + "color": "#FFFFFF" + } + }, + { + "widget_type": "image", + "x": 0.0, + "y": 0.0, + "w": 649.0, + "h": 864.0, + "z": 0.0, + "config": { + "imagePath": "D:/1-2.JPG" + } + }, + { + "widget_type": "web", + "x": 649.0, + "y": 0.0, + "w": 886.0, + "h": 863.0, + "z": 0.0, + "config": { + "url": "http://127.0.0.1:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h", + "locked": true + } + } + ] +} \ No newline at end of file diff --git a/configs/1000泵/template.docx.### b/configs/1000泵/template.docx.### deleted file mode 100644 index 396fc65..0000000 Binary files a/configs/1000泵/template.docx.### and /dev/null differ diff --git a/configs/600泵/config.json b/configs/600泵/config.json index 9723a53..0da5b6c 100644 --- a/configs/600泵/config.json +++ b/configs/600泵/config.json @@ -2,7 +2,7 @@ "influx": { "url": "http://10.0.5.232:8086", "org": "MEASCON", - "token": "_jtoxcVDIbol2Uqt_vlhidut-EO0Xo0ZXea2UC5a5Bgotk836F0xPN4NSGY1jYI_WaBKRau4RyZ-g2XSFiNdXw==", + "token": "JPZMq2UP5ORhLq8CfsPbawl6k0MSDlJmEwMJ2uvR_TXqW5bUOWIYBQOSXkGNzDqOU3rnuGpIxGxrB_mlAF-EEw==", "username": "PCM", "password": "1842moon", "landingUrl": "http://10.0.5.232:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h", diff --git a/configs/600泵/dashboard.json b/configs/600泵/dashboard.json new file mode 100644 index 0000000..6ec4c8a --- /dev/null +++ b/configs/600泵/dashboard.json @@ -0,0 +1,63 @@ +{ + "canvas": { + "x": 0.0, + "y": 0.0, + "w": 1920.0, + "h": 1080.0 + }, + "widgets": [ + { + "widget_type": "label", + "x": 173.0, + "y": 210.0, + "w": 162.0, + "h": 62.0, + "z": 1.0, + "config": { + "fieldName": "主轴承#3", + "prefix": "主轴承温度", + "suffix": " ℃", + "fontSize": 16, + "color": "#FFFFFF" + } + }, + { + "widget_type": "label", + "x": 46.0, + "y": 378.0, + "w": 162.0, + "h": 62.0, + "z": 1.0, + "config": { + "fieldName": "减速箱小轴承2", + "prefix": "减速箱小轴承温度", + "suffix": "℃", + "fontSize": 16, + "color": "#FFFFFF" + } + }, + { + "widget_type": "image", + "x": 0.0, + "y": 0.0, + "w": 649.0, + "h": 864.0, + "z": 0.0, + "config": { + "imagePath": "D:/1-2.JPG" + } + }, + { + "widget_type": "web", + "x": 649.0, + "y": 0.0, + "w": 886.0, + "h": 863.0, + "z": 0.0, + "config": { + "url": "http://127.0.0.1:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h", + "locked": true + } + } + ] +} \ No newline at end of file diff --git a/default.json b/default.json index a3f1b26..e9b7eeb 100644 --- a/default.json +++ b/default.json @@ -2,7 +2,7 @@ "influx": { "url": "http://10.0.5.232:8086", "org": "MEASCON", - "token": "_jtoxcVDIbol2Uqt_vlhidut-EO0Xo0ZXea2UC5a5Bgotk836F0xPN4NSGY1jYI_WaBKRau4RyZ-g2XSFiNdXw==", + "token": "JPZMq2UP5ORhLq8CfsPbawl6k0MSDlJmEwMJ2uvR_TXqW5bUOWIYBQOSXkGNzDqOU3rnuGpIxGxrB_mlAF-EEw==", "username": "PCM", "password": "1842moon", "landingUrl": "http://10.0.5.232:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h", @@ -10,152 +10,9 @@ "measurement": "PCM_Measurement" }, "placeholders": { - "chart1": { - "type": "chart", - "label": "chart1", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart2": { - "type": "chart", - "label": "chart2", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart3": { - "type": "chart", - "label": "chart3", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart4": { - "type": "chart", - "label": "chart4", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart5": { - "type": "chart", - "label": "chart5", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart6": { - "type": "chart", - "label": "chart6", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart7": { - "type": "chart", - "label": "chart7", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart8": { - "type": "chart", - "label": "chart8", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart9": { - "type": "chart", - "label": "chart9", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart10": { - "type": "chart", - "label": "chart10", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart11": { - "type": "chart", - "label": "chart11", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "chart12": { - "type": "chart", - "label": "chart12", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "table1": { - "type": "table", - "label": "table1", - "title": "", - "value": "", - "dbQuery": "", - "chart": {}, - "table": { - "firstColumn": "time", - "firstTitle": "", - "titles": {} - } - }, - "table2": { - "type": "table", - "label": "table2", - "title": "", - "value": "", - "dbQuery": "", - "chart": {}, - "table": { - "firstColumn": "time", - "firstTitle": "", - "titles": {} - } - }, - "table3": { - "type": "table", - "label": "table3", - "title": "", - "value": "", - "dbQuery": "", - "chart": {}, - "table": { - "firstColumn": "time", - "firstTitle": "", - "titles": {} - } - }, - "tb1": { - "type": "manualTable", - "label": "tb1", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "tb2": { - "type": "manualTable", - "label": "tb2", + "scriptTable1": { + "type": "scriptTable", + "label": "scriptTable1", "title": "", "value": "", "dbQuery": "", @@ -192,86 +49,6 @@ "value": "@executor", "dbQuery": "", "chart": {} - }, - "text5": { - "type": "text", - "label": "text5", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text6": { - "type": "text", - "label": "text6", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text7": { - "type": "text", - "label": "text7", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text8": { - "type": "text", - "label": "text8", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text9": { - "type": "text", - "label": "text9", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text10": { - "type": "text", - "label": "text10", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text11": { - "type": "text", - "label": "text11", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text12": { - "type": "text", - "label": "text12", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text13": { - "type": "text", - "label": "text13", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} - }, - "text14": { - "type": "text", - "label": "text14", - "title": "", - "value": "", - "dbQuery": "", - "chart": {} } }, "tcpModbus": { @@ -390,7 +167,7 @@ "10" ] ], - "scriptFile": "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uDQojIC0qLSBjb2Rpbmc6IHV0Zi04IC0qLQ0KIiIiDQrmtYvor5Xpg6jkvY3muKnluqborrDlvZXooajnlJ/miJDohJrmnKzvvIjluKbotJ/ovb3nirbmgIHnrZvpgInvvIkNCg0KLSDlv73nlaXkvKDlhaXnmoQgZXhwZXJpbWVudFByb2Nlc3PvvIzoh6rooYzmnoTpgKDlm7rlrprnu5PmnoTnmoTmlbDmja4NCi0g5LuOIEluZmx1eERCIOafpeivouavj+S4qua1i+ivlemDqOS9jeWcqOWQhOaXtumXtOeCueeahOeerOaXtua4qeW6puWAvA0KLSDmt7vliqAgbG9hZF9zdGF0dXMgPSAxIOeahOetm+mAieadoeS7tu+8jOehruS/neWPquWcqOecn+ato+mHh+mbhuaVsOaNruaXtuiOt+WPlua4qeW6pg0KLSDovpPlh7rmoLzlvI/kuI7lupTnlKjkuK3nmoQgc2NyaXB0VGFibGUg5Y2g5L2N56ym5YW85a65DQotIOm7mOiupOaKiiB7c2NyaXB0VGFibGUxfSDmlL7lnKgi5rWL6K+V6YOo5L2NIuaJgOWcqOeahOWNleWFg+agvA0KDQrnjq/looPlj5jph4/vvJoNCiAgICBUQUJMRV9UT0tFTiAgICAgICAgIOebruagh+WNoOS9jeespu+8jOm7mOiupCBzY3JpcHRUYWJsZTENCiAgICBUQUJMRV9TVEFSVF9ST1cgICAgIOWGmeWFpei1t+Wni+ihjOWBj+enu++8jOm7mOiupCAwDQogICAgVEFCTEVfU1RBUlRfQ09MICAgICDlhpnlhaXotbflp4vliJflgY/np7vvvIzpu5jorqQgMA0KICAgIFRBQkxFX1RJTUVfU0xPVFMgICAg6YCX5Y+35YiG6ZqU55qE5pe26Ze05Yi75bqm77yM6buY6K6kICIwLjVoLDFoLDEuNWgsMmgsMi41aCwzaCwzLjVoIg0KICAgIFRBQkxFX01PVE9SX1NQRUVEICAg55S15py66L2s6YCf5qCH562+77yM6buY6K6kICI5ODBSUE0iDQogICAgRVhQRVJJTUVOVF9TVEFSVCAgICAg5a6e6aqM5byA5aeL5pe26Ze077yISVNPIDg2MDEg5qC85byP77yM5aaCIDIwMjQtMDEtMDFUMTA6MDA6MDBa77yJDQogICAgRVhQRVJJTUVOVF9FTkQgICAgICAg5a6e6aqM57uT5p2f5pe26Ze077yISVNPIDg2MDEg5qC85byP77yJDQogICAgSU5GTFVYX1VSTCAgICAgICAgICAgSW5mbHV4REIgVVJMDQogICAgSU5GTFVYX09SRyAgICAgICAgICAgSW5mbHV4REIg57uE57uHDQogICAgSU5GTFVYX1RPS0VOICAgICAgICAgSW5mbHV4REIg5Luk54mMDQogICAgSU5GTFVYX0JVQ0tFVCAgICAgICAgSW5mbHV4REIg5qG25ZCN77yM6buY6K6kIFBDTQ0KICAgIElORkxVWF9NRUFTVVJFTUVOVCAgIEluZmx1eERCIOa1i+mHj+WQje+8jOm7mOiupCBQQ01fTWVhc3VyZW1lbnQNCiIiIg0KDQpmcm9tIF9fZnV0dXJlX18gaW1wb3J0IGFubm90YXRpb25zDQoNCmltcG9ydCBqc29uDQppbXBvcnQgbG9nZ2luZw0KaW1wb3J0IG9zDQppbXBvcnQgc3lzDQpmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZSwgdGltZWRlbHRhDQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBPcHRpb25hbA0KDQoNCkxPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKF9fbmFtZV9fKQ0KDQoNCmRlZiBfbWFza19zZWNyZXQodmFsdWU6IE9wdGlvbmFsW3N0cl0pIC0+IHN0cjoNCiAgICAiIiLmjqnnoIHmlY/mhJ/kv6Hmga8iIiINCiAgICBpZiBub3QgdmFsdWU6DQogICAgICAgIHJldHVybiAiPGVtcHR5PiINCiAgICBpZiBsZW4odmFsdWUpIDw9IDg6DQogICAgICAgIHJldHVybiAiKiIgKiBsZW4odmFsdWUpDQogICAgcmV0dXJuIHZhbHVlWzo0XSArICIqIiAqIChsZW4odmFsdWUpIC0gOCkgKyB2YWx1ZVstNDpdDQoNCg0KZGVmIF9zZXR1cF9sb2dnaW5nKCkgLT4gTm9uZToNCiAgICAiIiLorr7nva7ml6Xlv5ciIiINCiAgICBsb2dfbGV2ZWxfc3RyID0gb3MuZW52aXJvbi5nZXQoIlRBQkxFX0xPR19MRVZFTCIsICJERUJVRyIpLnVwcGVyKCkNCiAgICBsb2dfbGV2ZWwgPSBnZXRhdHRyKGxvZ2dpbmcsIGxvZ19sZXZlbF9zdHIsIGxvZ2dpbmcuREVCVUcpDQogICAgDQogICAgIyDphY3nva7moLnml6Xlv5forrDlvZXlmagNCiAgICBsb2dnaW5nLmJhc2ljQ29uZmlnKA0KICAgICAgICBsZXZlbD1sb2dfbGV2ZWwsDQogICAgICAgIGZvcm1hdD0nJShhc2N0aW1lKXMgWyUobGV2ZWxuYW1lKXNdICUobmFtZSlzOiAlKG1lc3NhZ2UpcycsDQogICAgICAgIGhhbmRsZXJzPVsNCiAgICAgICAgICAgIGxvZ2dpbmcuU3RyZWFtSGFuZGxlcihzeXMuc3RkZXJyKQ0KICAgICAgICBdDQogICAgKQ0KICAgIA0KICAgICMg5aaC5p6c5oyH5a6a5LqG5pel5b+X5paH5Lu277yM5re75Yqg5paH5Lu25aSE55CG5ZmoDQogICAgbG9nX2ZpbGUgPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfTE9HX0ZJTEUiLCAiIikuc3RyaXAoKQ0KICAgIGlmIGxvZ19maWxlOg0KICAgICAgICB0cnk6DQogICAgICAgICAgICBmaWxlX2hhbmRsZXIgPSBsb2dnaW5nLkZpbGVIYW5kbGVyKGxvZ19maWxlLCBlbmNvZGluZz0ndXRmLTgnKQ0KICAgICAgICAgICAgZmlsZV9oYW5kbGVyLnNldExldmVsKGxvZ19sZXZlbCkNCiAgICAgICAgICAgIGZpbGVfaGFuZGxlci5zZXRGb3JtYXR0ZXIobG9nZ2luZy5Gb3JtYXR0ZXIoDQogICAgICAgICAgICAgICAgJyUoYXNjdGltZSlzIFslKGxldmVsbmFtZSlzXSAlKG5hbWUpczogJShtZXNzYWdlKXMnDQogICAgICAgICAgICApKQ0KICAgICAgICAgICAgbG9nZ2luZy5nZXRMb2dnZXIoKS5hZGRIYW5kbGVyKGZpbGVfaGFuZGxlcikNCiAgICAgICAgICAgIExPR0dFUi5pbmZvKCLml6Xlv5fmlofku7blt7LphY3nva46ICVzIiwgbG9nX2ZpbGUpDQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToNCiAgICAgICAgICAgIExPR0dFUi53YXJuaW5nKCLphY3nva7ml6Xlv5fmlofku7blpLHotKU6ICVzIiwgZSkNCg0KDQpkZWYgX2dldF9pbmZsdXhfY29uZmlnKCkgLT4gRGljdFtzdHIsIHN0cl06DQogICAgIiIi6I635Y+WSW5mbHV4RELphY3nva4iIiINCiAgICBjb25maWcgPSB7DQogICAgICAgICd1cmwnOiBvcy5lbnZpcm9uLmdldCgiSU5GTFVYX1VSTCIsICIiKS5zdHJpcCgpLA0KICAgICAgICAnb3JnJzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9PUkciLCAiIikuc3RyaXAoKSwNCiAgICAgICAgJ3Rva2VuJzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9UT0tFTiIsICIiKS5zdHJpcCgpLA0KICAgICAgICAnYnVja2V0Jzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9CVUNLRVQiLCAiUENNIikuc3RyaXAoKSwNCiAgICAgICAgJ21lYXN1cmVtZW50Jzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9NRUFTVVJFTUVOVCIsICJQQ01fTWVhc3VyZW1lbnQiKS5zdHJpcCgpLA0KICAgIH0NCiAgICANCiAgICBMT0dHRVIuZGVidWcoDQogICAgICAgICJJbmZsdXhEQumFjee9rjogdXJsPSVzIG9yZz0lcyB0b2tlbj0lcyBidWNrZXQ9JXMgbWVhc3VyZW1lbnQ9JXMiLA0KICAgICAgICBjb25maWdbJ3VybCddIG9yICI8ZW1wdHk+IiwNCiAgICAgICAgY29uZmlnWydvcmcnXSBvciAiPGVtcHR5PiIsDQogICAgICAgIF9tYXNrX3NlY3JldChjb25maWdbJ3Rva2VuJ10pLA0KICAgICAgICBjb25maWdbJ2J1Y2tldCddLA0KICAgICAgICBjb25maWdbJ21lYXN1cmVtZW50J10sDQogICAgKQ0KICAgIA0KICAgIHJldHVybiBjb25maWcNCg0KDQpkZWYgX3BhcnNlX2V4cGVyaW1lbnRfdGltZXMoKSAtPiB0dXBsZVtPcHRpb25hbFtkYXRldGltZV0sIE9wdGlvbmFsW2RhdGV0aW1lXV06DQogICAgIiIi6Kej5p6Q5a6e6aqM5pe26Ze0IiIiDQogICAgc3RhcnRfc3RyID0gb3MuZW52aXJvbi5nZXQoIkVYUEVSSU1FTlRfU1RBUlQiLCAiIikuc3RyaXAoKQ0KICAgIGVuZF9zdHIgPSBvcy5lbnZpcm9uLmdldCgiRVhQRVJJTUVOVF9FTkQiLCAiIikuc3RyaXAoKQ0KICAgIA0KICAgIHN0YXJ0X3RpbWU6IE9wdGlvbmFsW2RhdGV0aW1lXSA9IE5vbmUNCiAgICBlbmRfdGltZTogT3B0aW9uYWxbZGF0ZXRpbWVdID0gTm9uZQ0KICAgIA0KICAgIGlmIHN0YXJ0X3N0cjoNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgZm9yIGZtdCBpbiBbIiVZLSVtLSVkVCVIOiVNOiVTWiIsICIlWS0lbS0lZFQlSDolTTolUyV6Il06DQogICAgICAgICAgICAgICAgdHJ5Og0KICAgICAgICAgICAgICAgICAgICBzdGFydF90aW1lID0gZGF0ZXRpbWUuc3RycHRpbWUoc3RhcnRfc3RyLCBmbXQpDQogICAgICAgICAgICAgICAgICAgIGlmIHN0YXJ0X3RpbWUudHppbmZvIGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgICAgICAgICAgICAgIyDovazmjaLkuLrmnKzlnLDml7bpl7TlubbljrvpmaTml7bljLrkv6Hmga8NCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0X3RpbWUgPSBzdGFydF90aW1lLmFzdGltZXpvbmUodHo9Tm9uZSkucmVwbGFjZSh0emluZm89Tm9uZSkNCiAgICAgICAgICAgICAgICAgICAgYnJlYWsNCiAgICAgICAgICAgICAgICBleGNlcHQgVmFsdWVFcnJvcjoNCiAgICAgICAgICAgICAgICAgICAgY29udGludWUNCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOg0KICAgICAgICAgICAgcHJpbnQoZiJXYXJuaW5nOiBGYWlsZWQgdG8gcGFyc2UgRVhQRVJJTUVOVF9TVEFSVCAne3N0YXJ0X3N0cn0nOiB7ZX0iLCBmaWxlPXN5cy5zdGRlcnIpDQogICAgDQogICAgaWYgZW5kX3N0cjoNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgZm9yIGZtdCBpbiBbIiVZLSVtLSVkVCVIOiVNOiVTWiIsICIlWS0lbS0lZFQlSDolTTolUyV6Il06DQogICAgICAgICAgICAgICAgdHJ5Og0KICAgICAgICAgICAgICAgICAgICBlbmRfdGltZSA9IGRhdGV0aW1lLnN0cnB0aW1lKGVuZF9zdHIsIGZtdCkNCiAgICAgICAgICAgICAgICAgICAgaWYgZW5kX3RpbWUudHppbmZvIGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgICAgICAgICAgICAgIyDovazmjaLkuLrmnKzlnLDml7bpl7TlubbljrvpmaTml7bljLrkv6Hmga8NCiAgICAgICAgICAgICAgICAgICAgICAgIGVuZF90aW1lID0gZW5kX3RpbWUuYXN0aW1lem9uZSh0ej1Ob25lKS5yZXBsYWNlKHR6aW5mbz1Ob25lKQ0KICAgICAgICAgICAgICAgICAgICBicmVhaw0KICAgICAgICAgICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOg0KICAgICAgICAgICAgICAgICAgICBjb250aW51ZQ0KICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6DQogICAgICAgICAgICBwcmludChmIldhcm5pbmc6IEZhaWxlZCB0byBwYXJzZSBFWFBFUklNRU5UX0VORCAne2VuZF9zdHJ9Jzoge2V9IiwgZmlsZT1zeXMuc3RkZXJyKQ0KICAgIA0KICAgIHJldHVybiBzdGFydF90aW1lLCBlbmRfdGltZQ0KDQoNCmRlZiBfcGFyc2VfdGltZV9zbG90KHNsb3Rfc3RyOiBzdHIpIC0+IGZsb2F0Og0KICAgICIiIuino+aekOaXtumXtOanveWtl+espuS4suS4uuWwj+aXtuaVsCIiIg0KICAgIGlmIG5vdCBzbG90X3N0cjoNCiAgICAgICAgcmV0dXJuIDAuMA0KICAgIA0KICAgIHNsb3Rfc3RyID0gc2xvdF9zdHIuc3RyaXAoKS5sb3dlcigpDQogICAgDQogICAgaWYgc2xvdF9zdHIuZW5kc3dpdGgoJ2gnKToNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgcmV0dXJuIGZsb2F0KHNsb3Rfc3RyWzotMV0pDQogICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOg0KICAgICAgICAgICAgcGFzcw0KICAgIA0KICAgIHRyeToNCiAgICAgICAgcmV0dXJuIGZsb2F0KHNsb3Rfc3RyKQ0KICAgIGV4Y2VwdCBWYWx1ZUVycm9yOg0KICAgICAgICBwYXNzDQogICAgDQogICAgcmV0dXJuIDAuMA0KDQoNCmRlZiBfdGltZV9zbG90cygpIC0+IExpc3Rbc3RyXToNCiAgICByYXcgPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfVElNRV9TTE9UUyIsICIiKS5zdHJpcCgpDQogICAgaWYgbm90IHJhdzoNCiAgICAgICAgIyDmoLnmja7lm77niYfvvIzml7bpl7TliLvluqbmmK/vvJowLjVoLCAxaCwgMS41aCwgMmgsIDIuNWgsIDNoLCAzLjVo77yIN+WIl++8iQ0KICAgICAgICByZXR1cm4gWyIwLjVoIiwgIjFoIiwgIjEuNWgiLCAiMmgiLCAiMi41aCIsICIzaCIsICIzLjVoIl0NCiAgICBzbG90cyA9IFtzbG90LnN0cmlwKCkgZm9yIHNsb3QgaW4gcmF3LnNwbGl0KCIsIildDQogICAgcmV0dXJuIFtzbG90IGZvciBzbG90IGluIHNsb3RzIGlmIHNsb3RdDQoNCg0KZGVmIF9kZWZhdWx0X3NlY3Rpb25zKCkgLT4gTGlzdFtEaWN0W3N0ciwgQW55XV06DQogICAgIyBuYW1lIC0+IHJvd3MgdW5kZXJuZWF0aO+8iGVudHJpZXPvvIkNCiAgICAjIOavj+S4qiBlbnRyeSDlr7nlupTkuIDkuKrmtYvor5Xpg6jkvY3vvIzpnIDopoHmmKDlsITliLAgSW5mbHV4REIg55qEIGZpZWxkIOaIliB0YWcNCiAgICByZXR1cm4gWw0KICAgICAgICB7Im5hbWUiOiAi5Li76L205om/IiwgImVudHJpZXMiOiBbDQogICAgICAgICAgICB7ImxhYmVsIjogIiMxIiwgImZpZWxkIjogIuS4u+i9tOaJvyMxIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLkuLvovbTmib8jMSJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjMiIsICJmaWVsZCI6ICLkuLvovbTmib8jMiIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5Li76L205om/IzIifSwNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzMiLCAiZmllbGQiOiAi5Li76L205om/IzMiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuS4u+i9tOaJvyMzIn0sDQogICAgICAgICAgICB7ImxhYmVsIjogIiM0IiwgImZpZWxkIjogIuS4u+i9tOaJvyM0IiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLkuLvovbTmib8jNCJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjNSIsICJmaWVsZCI6ICLkuLvovbTmib8jNSIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5Li76L205om/IzUifSwNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzYiLCAiZmllbGQiOiAi5Li76L205om/IzYiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuS4u+i9tOaJvyM2In0sDQogICAgICAgIF19LA0KICAgICAgICB7Im5hbWUiOiAi5Y2B5a2X5aS0IiwgImVudHJpZXMiOiBbDQogICAgICAgICAgICB7ImxhYmVsIjogIiMxIiwgImZpZWxkIjogIuWNgeWtl+WktCMxIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLljYHlrZflpLQjMSJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjMiIsICJmaWVsZCI6ICLljYHlrZflpLQjMiIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5Y2B5a2X5aS0IzIifSwNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzMiLCAiZmllbGQiOiAi5Y2B5a2X5aS0IzMiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuWNgeWtl+WktCMzIn0sDQogICAgICAgICAgICB7ImxhYmVsIjogIiM0IiwgImZpZWxkIjogIuWNgeWtl+WktCM0IiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLljYHlrZflpLQjNCJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjNSIsICJmaWVsZCI6ICLljYHlrZflpLQjNSIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5Y2B5a2X5aS0IzUifSwNCiAgICAgICAgXX0sDQogICAgICAgIHsibmFtZSI6ICLlh4/pgJ/nrrHlsI/ovbTmib8iLCAiZW50cmllcyI6IFsNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzHvvIjovpPlhaXms5XlhbDnq6/vvIkiLCAiZmllbGQiOiAi5YeP6YCf566x5bCP6L205om/MSIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5YeP6YCf566x5bCP6L205om/IzEifSwNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzIiLCAiZmllbGQiOiAi5YeP6YCf566x5bCP6L205om/IzIiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuWHj+mAn+euseWwj+i9tOaJvyMyIn0sDQogICAgICAgIF19LA0KICAgICAgICB7Im5hbWUiOiAi5YeP6YCf566x5aSn6L205om/IiwgImVudHJpZXMiOiBbDQogICAgICAgICAgICB7ImxhYmVsIjogIiMz77yI5aSn56uv55uW56uv77yJIiwgImZpZWxkIjogIuWHj+mAn+euseWkp+i9tOaJvyMzIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLlh4/pgJ/nrrHlpKfovbTmib8jMyJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjNCIsICJmaWVsZCI6ICLlh4/pgJ/nrrHlpKfovbTmib8jNCIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5YeP6YCf566x5aSn6L205om/IzQifSwNCiAgICAgICAgXX0sDQogICAgICAgIHsibmFtZSI6ICLmtqbmu5HmsrnmuKkiLCAiZW50cmllcyI6IFsNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIiwgImZpZWxkIjogIm1lYW4iLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIua2pua7keayuea4qSJ9LCAicmVzdWx0X2tleSI6ICLmtqbmu5HmsrnmuKkifSwNCiAgICAgICAgXX0sDQogICAgICAgIHsibmFtZSI6ICLmtqbmu5HmsrnljosiLCAiZW50cmllcyI6IFsNCiAgICAgICAgICAgIHsibGFiZWwiOiAiKFBzaSkiLCAiZmllbGQiOiAibWVhbiIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAi5ram5ruR5rK55Y6LIn0sICJyZXN1bHRfa2V5IjogIua2pua7keayueWOiyJ9LA0KICAgICAgICBdfSwNCiAgICBdDQoNCmRlZiBfcXVlcnlfbG9hZF9zdGF0dXNfdGltZWxpbmUoDQogICAgc3RhcnRfdGltZTogZGF0ZXRpbWUsDQogICAgZW5kX3RpbWU6IGRhdGV0aW1lLA0KICAgIGluZmx1eF91cmw6IHN0ciwNCiAgICBpbmZsdXhfb3JnOiBzdHIsDQogICAgaW5mbHV4X3Rva2VuOiBzdHIsDQogICAgaW5mbHV4X2J1Y2tldDogc3RyLA0KICAgIGluZmx1eF9tZWFzdXJlbWVudDogc3RyLA0KKSAtPiBMaXN0W0RpY3Rbc3RyLCBBbnldXToNCiAgICAiIiLmn6Xor6LmlbTkuKrlrp7pqozmnJ/pl7TnmoRsb2FkX3N0YXR1c+aXtumXtOe6v+aVsOaNriIiIg0KICAgIHRyeToNCiAgICAgICAgZnJvbSBpbmZsdXhkYl9jbGllbnQgaW1wb3J0IEluZmx1eERCQ2xpZW50DQogICAgICAgIGltcG9ydCBwYW5kYXMgYXMgcGQNCiAgICAgICAgaW1wb3J0IHdhcm5pbmdzDQogICAgICAgIGZyb20gaW5mbHV4ZGJfY2xpZW50LmNsaWVudC53YXJuaW5ncyBpbXBvcnQgTWlzc2luZ1Bpdm90RnVuY3Rpb24NCiAgICBleGNlcHQgSW1wb3J0RXJyb3I6DQogICAgICAgIExPR0dFUi53YXJuaW5nKCJJbmZsdXhEQiBjbGllbnQgbm90IGF2YWlsYWJsZSwgc2tpcCBsb2FkX3N0YXR1cyB0aW1lbGluZSBxdWVyeSIpDQogICAgICAgIHJldHVybiBbXQ0KDQogICAgdHJ5Og0KICAgICAgICBjbGllbnQgPSBJbmZsdXhEQkNsaWVudCh1cmw9aW5mbHV4X3VybCwgb3JnPWluZmx1eF9vcmcsIHRva2VuPWluZmx1eF90b2tlbikNCiAgICAgICAgcXVlcnlfYXBpID0gY2xpZW50LnF1ZXJ5X2FwaSgpDQoNCiAgICAgICAgc3RhcnRfcmZjID0gc3RhcnRfdGltZS5zdHJmdGltZSgnJVktJW0tJWRUJUg6JU06JVNaJykNCiAgICAgICAgZW5kX3JmYyA9IGVuZF90aW1lLnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKQ0KDQogICAgICAgICMg5p+l6K+ibG9hZF9zdGF0dXPlrZfmrrXnmoTmiYDmnInmlbDmja7ngrnvvIjlnKhCcmVha2Vy5pWw5o2u57G75Z6L5Lit77yJDQogICAgICAgIGZsdXggPSBmJycnDQpmcm9tKGJ1Y2tldDogIntpbmZsdXhfYnVja2V0fSIpDQogIHw+IHJhbmdlKHN0YXJ0OiB7c3RhcnRfcmZjfSwgc3RvcDoge2VuZF9yZmN9KQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJfbWVhc3VyZW1lbnQiXSA9PSAie2luZmx1eF9tZWFzdXJlbWVudH0iKQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJkYXRhX3R5cGUiXSA9PSAiQnJlYWtlciIpDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl9maWVsZCJdID09ICJsb2FkX3N0YXR1cyIpDQogIHw+IHNvcnQoY29sdW1uczogWyJfdGltZSJdKQ0KICB8PiB5aWVsZChuYW1lOiAibG9hZF9zdGF0dXNfdGltZWxpbmUiKQ0KJycnLnN0cmlwKCkNCg0KICAgICAgICBMT0dHRVIuZGVidWcoIkxvYWQgc3RhdHVzIHRpbWVsaW5lIHF1ZXJ5OlxuJXMiLCBmbHV4KQ0KDQogICAgICAgIHdpdGggd2FybmluZ3MuY2F0Y2hfd2FybmluZ3MoKToNCiAgICAgICAgICAgIHdhcm5pbmdzLnNpbXBsZWZpbHRlcigiaWdub3JlIiwgTWlzc2luZ1Bpdm90RnVuY3Rpb24pDQogICAgICAgICAgICBmcmFtZXMgPSBxdWVyeV9hcGkucXVlcnlfZGF0YV9mcmFtZShmbHV4KQ0KICAgICAgICANCiAgICAgICAgaWYgaXNpbnN0YW5jZShmcmFtZXMsIGxpc3QpOg0KICAgICAgICAgICAgZGYgPSBwZC5jb25jYXQoZnJhbWVzLCBpZ25vcmVfaW5kZXg9VHJ1ZSkgaWYgZnJhbWVzIGVsc2UgcGQuRGF0YUZyYW1lKCkNCiAgICAgICAgZWxzZToNCiAgICAgICAgICAgIGRmID0gZnJhbWVzDQoNCiAgICAgICAgaWYgZGYuZW1wdHkgb3IgJ192YWx1ZScgbm90IGluIGRmLmNvbHVtbnMgb3IgJ190aW1lJyBub3QgaW4gZGYuY29sdW1uczoNCiAgICAgICAgICAgIExPR0dFUi53YXJuaW5nKCJObyBsb2FkX3N0YXR1cyB0aW1lbGluZSBkYXRhIGZvdW5kIikNCiAgICAgICAgICAgIHJldHVybiBbXQ0KDQogICAgICAgICMg6L2s5o2i5Li65pe26Ze057q/5pWw5o2u77yM56Gu5L+d5pe25Yy65LiA6Ie05oCnDQogICAgICAgIHRpbWVsaW5lID0gW10NCiAgICAgICAgZm9yIF8sIHJvdyBpbiBkZi5pdGVycm93cygpOg0KICAgICAgICAgICAgdGltZV9vYmogPSBwZC50b19kYXRldGltZShyb3dbJ190aW1lJ10pDQogICAgICAgICAgICAjIOi9rOaNouS4uuacrOWcsOaXtumXtO+8jOWOu+mZpOaXtuWMuuS/oeaBr++8jOS4jnN0YXJ0X3RpbWUvZW5kX3RpbWXkv53mjIHkuIDoh7QNCiAgICAgICAgICAgIGlmIGhhc2F0dHIodGltZV9vYmosICd0eicpIGFuZCB0aW1lX29iai50eiBpcyBub3QgTm9uZToNCiAgICAgICAgICAgICAgICAjIOWvueS6jnBhbmRhcyBUaW1lc3RhbXDvvIzlhYjovazmjaLkuLrmnKzlnLDml7bljLrlho3ovazkuLpQeXRob24gZGF0ZXRpbWUNCiAgICAgICAgICAgICAgICB0aW1lX29iaiA9IHRpbWVfb2JqLnR6X2NvbnZlcnQoTm9uZSkudG9fcHlkYXRldGltZSgpDQogICAgICAgICAgICBlbGlmIGhhc2F0dHIodGltZV9vYmosICd0b19weWRhdGV0aW1lJyk6DQogICAgICAgICAgICAgICAgIyDovazmjaLkuLpQeXRob24gZGF0ZXRpbWXlr7nosaENCiAgICAgICAgICAgICAgICB0aW1lX29iaiA9IHRpbWVfb2JqLnRvX3B5ZGF0ZXRpbWUoKQ0KICAgICAgICAgICAgDQogICAgICAgICAgICAjIOehruS/neayoeacieaXtuWMuuS/oeaBrw0KICAgICAgICAgICAgaWYgaGFzYXR0cih0aW1lX29iaiwgJ3R6aW5mbycpIGFuZCB0aW1lX29iai50emluZm8gaXMgbm90IE5vbmU6DQogICAgICAgICAgICAgICAgdGltZV9vYmogPSB0aW1lX29iai5yZXBsYWNlKHR6aW5mbz1Ob25lKQ0KICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgdGltZWxpbmUuYXBwZW5kKHsNCiAgICAgICAgICAgICAgICAndGltZSc6IHRpbWVfb2JqLA0KICAgICAgICAgICAgICAgICdsb2FkX3N0YXR1cyc6IGZsb2F0KHJvd1snX3ZhbHVlJ10pDQogICAgICAgICAgICB9KQ0KDQogICAgICAgIExPR0dFUi5pbmZvKCJMb2FkIHN0YXR1cyB0aW1lbGluZTogJWQgZGF0YSBwb2ludHMgZnJvbSAlcyB0byAlcyIsIA0KICAgICAgICAgICAgICAgICAgIGxlbih0aW1lbGluZSksIHN0YXJ0X3RpbWUsIGVuZF90aW1lKQ0KICAgICAgICANCiAgICAgICAgIyDosIPor5XvvJrmo4Dmn6Xml7bpl7Tlr7nosaHnsbvlnosNCiAgICAgICAgaWYgdGltZWxpbmU6DQogICAgICAgICAgICBmaXJzdF90aW1lID0gdGltZWxpbmVbMF1bJ3RpbWUnXQ0KICAgICAgICAgICAgTE9HR0VSLmRlYnVnKCJUaW1lbGluZSBmaXJzdCB0aW1lOiAlcyAodHlwZTogJXMsIHR6aW5mbzogJXMpIiwgDQogICAgICAgICAgICAgICAgICAgICAgICBmaXJzdF90aW1lLCB0eXBlKGZpcnN0X3RpbWUpLCBnZXRhdHRyKGZpcnN0X3RpbWUsICd0emluZm8nLCBOb25lKSkNCiAgICAgICAgTE9HR0VSLmRlYnVnKCJzdGFydF90aW1lOiAlcyAodHlwZTogJXMsIHR6aW5mbzogJXMpIiwgDQogICAgICAgICAgICAgICAgICAgIHN0YXJ0X3RpbWUsIHR5cGUoc3RhcnRfdGltZSksIGdldGF0dHIoc3RhcnRfdGltZSwgJ3R6aW5mbycsIE5vbmUpKQ0KICAgICAgICBMT0dHRVIuZGVidWcoImVuZF90aW1lOiAlcyAodHlwZTogJXMsIHR6aW5mbzogJXMpIiwgDQogICAgICAgICAgICAgICAgICAgIGVuZF90aW1lLCB0eXBlKGVuZF90aW1lKSwgZ2V0YXR0cihlbmRfdGltZSwgJ3R6aW5mbycsIE5vbmUpKQ0KICAgICAgICANCiAgICAgICAgcmV0dXJuIHRpbWVsaW5lDQoNCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6DQogICAgICAgIExPR0dFUi5lcnJvcigiRXJyb3IgcXVlcnlpbmcgbG9hZF9zdGF0dXMgdGltZWxpbmU6ICVzIiwgZSkNCiAgICAgICAgcmV0dXJuIFtdDQogICAgZmluYWxseToNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgY2xpZW50LmNsb3NlKCkNCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoNCiAgICAgICAgICAgIHBhc3MNCg0KDQpkZWYgX2NhbGN1bGF0ZV9lZmZlY3RpdmVfdGltZV9wb2ludHMoDQogICAgc3RhcnRfdGltZTogZGF0ZXRpbWUsDQogICAgZW5kX3RpbWU6IGRhdGV0aW1lLA0KICAgIHRpbWVfc2xvdHM6IExpc3Rbc3RyXSwNCiAgICBpbmZsdXhfY29uZmlnOiBEaWN0W3N0ciwgc3RyXQ0KKSAtPiBEaWN0W3N0ciwgT3B0aW9uYWxbZGF0ZXRpbWVdXToNCiAgICAiIiLorqHnrpfln7rkuo7mnInmlYjov5DooYzml7bpl7TntK/orqHnmoTnnJ/lrp7ml7bpl7TngrkiIiINCiAgICANCiAgICAjIDEuIOiOt+WPlmxvYWRfc3RhdHVz5pe26Ze057q/DQogICAgdGltZWxpbmUgPSBfcXVlcnlfbG9hZF9zdGF0dXNfdGltZWxpbmUoDQogICAgICAgIHN0YXJ0X3RpbWUsIGVuZF90aW1lLA0KICAgICAgICBpbmZsdXhfY29uZmlnWyd1cmwnXSwgaW5mbHV4X2NvbmZpZ1snb3JnJ10sIGluZmx1eF9jb25maWdbJ3Rva2VuJ10sDQogICAgICAgIGluZmx1eF9jb25maWdbJ2J1Y2tldCddLCBpbmZsdXhfY29uZmlnWydtZWFzdXJlbWVudCddDQogICAgKQ0KICAgIA0KICAgIGlmIG5vdCB0aW1lbGluZToNCiAgICAgICAgTE9HR0VSLndhcm5pbmcoIk5vIGxvYWRfc3RhdHVzIHRpbWVsaW5lIGRhdGEsIGZhbGxiYWNrIHRvIG9yaWdpbmFsIHRpbWUgY2FsY3VsYXRpb24iKQ0KICAgICAgICAjIOWbnumAgOWIsOWOn+Wni+aXtumXtOiuoeeulw0KICAgICAgICByZXN1bHQgPSB7fQ0KICAgICAgICBmb3Igc2xvdF9zdHIgaW4gdGltZV9zbG90czoNCiAgICAgICAgICAgIHNsb3RfaG91cnMgPSBfcGFyc2VfdGltZV9zbG90KHNsb3Rfc3RyKQ0KICAgICAgICAgICAgcmVzdWx0W3Nsb3Rfc3RyXSA9IHN0YXJ0X3RpbWUgKyB0aW1lZGVsdGEoaG91cnM9c2xvdF9ob3VycykNCiAgICAgICAgcmV0dXJuIHJlc3VsdA0KICAgIA0KICAgICMgMi4g6K6h566X5pyJ5pWI6L+Q6KGM5pe26Ze05q61DQogICAgZWZmZWN0aXZlX3BlcmlvZHMgPSBbXQ0KICAgIGN1cnJlbnRfcGVyaW9kX3N0YXJ0ID0gTm9uZQ0KICAgIA0KICAgIGZvciBpLCBwb2ludCBpbiBlbnVtZXJhdGUodGltZWxpbmUpOg0KICAgICAgICBpZiBwb2ludFsnbG9hZF9zdGF0dXMnXSA9PSAxLjA6DQogICAgICAgICAgICBpZiBjdXJyZW50X3BlcmlvZF9zdGFydCBpcyBOb25lOg0KICAgICAgICAgICAgICAgIGN1cnJlbnRfcGVyaW9kX3N0YXJ0ID0gcG9pbnRbJ3RpbWUnXQ0KICAgICAgICBlbHNlOiAgIyBsb2FkX3N0YXR1cyAhPSAxLjANCiAgICAgICAgICAgIGlmIGN1cnJlbnRfcGVyaW9kX3N0YXJ0IGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgICAgIGVmZmVjdGl2ZV9wZXJpb2RzLmFwcGVuZCh7DQogICAgICAgICAgICAgICAgICAgICdzdGFydCc6IGN1cnJlbnRfcGVyaW9kX3N0YXJ0LA0KICAgICAgICAgICAgICAgICAgICAnZW5kJzogcG9pbnRbJ3RpbWUnXSwNCiAgICAgICAgICAgICAgICAgICAgJ2R1cmF0aW9uX2hvdXJzJzogKHBvaW50Wyd0aW1lJ10gLSBjdXJyZW50X3BlcmlvZF9zdGFydCkudG90YWxfc2Vjb25kcygpIC8gMzYwMC4wDQogICAgICAgICAgICAgICAgfSkNCiAgICAgICAgICAgICAgICBjdXJyZW50X3BlcmlvZF9zdGFydCA9IE5vbmUNCiAgICANCiAgICAjIOWkhOeQhuacgOWQjuS4gOS4quWRqOacn++8iOWmguaenOWunumqjOe7k+adn+aXtuS7jeWcqOi/kOihjO+8iQ0KICAgIGlmIGN1cnJlbnRfcGVyaW9kX3N0YXJ0IGlzIG5vdCBOb25lOg0KICAgICAgICBlZmZlY3RpdmVfcGVyaW9kcy5hcHBlbmQoew0KICAgICAgICAgICAgJ3N0YXJ0JzogY3VycmVudF9wZXJpb2Rfc3RhcnQsDQogICAgICAgICAgICAnZW5kJzogZW5kX3RpbWUsDQogICAgICAgICAgICAnZHVyYXRpb25faG91cnMnOiAoZW5kX3RpbWUgLSBjdXJyZW50X3BlcmlvZF9zdGFydCkudG90YWxfc2Vjb25kcygpIC8gMzYwMC4wDQogICAgICAgIH0pDQogICAgDQogICAgdG90YWxfZWZmZWN0aXZlX2hvdXJzID0gc3VtKHBlcmlvZFsnZHVyYXRpb25faG91cnMnXSBmb3IgcGVyaW9kIGluIGVmZmVjdGl2ZV9wZXJpb2RzKQ0KICAgIExPR0dFUi5pbmZvKCJFZmZlY3RpdmUgcnVubmluZyBwZXJpb2RzOiAlZCBwZXJpb2RzLCB0b3RhbCAlLjNmIGhvdXJzIiwgDQogICAgICAgICAgICAgICBsZW4oZWZmZWN0aXZlX3BlcmlvZHMpLCB0b3RhbF9lZmZlY3RpdmVfaG91cnMpDQogICAgDQogICAgZm9yIHBlcmlvZCBpbiBlZmZlY3RpdmVfcGVyaW9kczoNCiAgICAgICAgTE9HR0VSLmRlYnVnKCJFZmZlY3RpdmUgcGVyaW9kOiAlcyDihpIgJXMgKCUuM2YgaG91cnMpIiwNCiAgICAgICAgICAgICAgICAgICAgcGVyaW9kWydzdGFydCddLnN0cmZ0aW1lKCclSDolTTolUycpLA0KICAgICAgICAgICAgICAgICAgICBwZXJpb2RbJ2VuZCddLnN0cmZ0aW1lKCclSDolTTolUycpLA0KICAgICAgICAgICAgICAgICAgICBwZXJpb2RbJ2R1cmF0aW9uX2hvdXJzJ10pDQogICAgDQogICAgIyAzLiDorqHnrpfmr4/kuKrml7bpl7Tmp73lr7nlupTnmoTnnJ/lrp7ml7bpl7TngrkNCiAgICBlZmZlY3RpdmVfdGltZV9wb2ludHMgPSB7fQ0KICAgIA0KICAgIGZvciBzbG90X3N0ciBpbiB0aW1lX3Nsb3RzOg0KICAgICAgICB0YXJnZXRfZWZmZWN0aXZlX2hvdXJzID0gX3BhcnNlX3RpbWVfc2xvdChzbG90X3N0cikNCiAgICAgICAgDQogICAgICAgIGlmIHRhcmdldF9lZmZlY3RpdmVfaG91cnMgPD0gMDoNCiAgICAgICAgICAgIGVmZmVjdGl2ZV90aW1lX3BvaW50c1tzbG90X3N0cl0gPSBOb25lDQogICAgICAgICAgICBjb250aW51ZQ0KICAgICAgICANCiAgICAgICAgaWYgdGFyZ2V0X2VmZmVjdGl2ZV9ob3VycyA+IHRvdGFsX2VmZmVjdGl2ZV9ob3VyczoNCiAgICAgICAgICAgIExPR0dFUi53YXJuaW5nKCJUYXJnZXQgZWZmZWN0aXZlIHRpbWUgJS4zZmggZXhjZWVkcyB0b3RhbCBlZmZlY3RpdmUgdGltZSAlLjNmaCBmb3Igc2xvdCAlcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldF9lZmZlY3RpdmVfaG91cnMsIHRvdGFsX2VmZmVjdGl2ZV9ob3Vycywgc2xvdF9zdHIpDQogICAgICAgICAgICBlZmZlY3RpdmVfdGltZV9wb2ludHNbc2xvdF9zdHJdID0gTm9uZQ0KICAgICAgICAgICAgY29udGludWUNCiAgICAgICAgDQogICAgICAgICMg5Zyo5pyJ5pWI5pe26Ze05q615Lit5p+l5om+57Sv6K6h6L+Q6KGMdGFyZ2V0X2VmZmVjdGl2ZV9ob3Vyc+Wwj+aXtueahOaXtumXtOeCuQ0KICAgICAgICBjdW11bGF0aXZlX2hvdXJzID0gMC4wDQogICAgICAgIHRhcmdldF90aW1lX3BvaW50ID0gTm9uZQ0KICAgICAgICANCiAgICAgICAgZm9yIHBlcmlvZCBpbiBlZmZlY3RpdmVfcGVyaW9kczoNCiAgICAgICAgICAgIHBlcmlvZF9kdXJhdGlvbiA9IHBlcmlvZFsnZHVyYXRpb25faG91cnMnXQ0KICAgICAgICAgICAgDQogICAgICAgICAgICBpZiBjdW11bGF0aXZlX2hvdXJzICsgcGVyaW9kX2R1cmF0aW9uID49IHRhcmdldF9lZmZlY3RpdmVfaG91cnM6DQogICAgICAgICAgICAgICAgIyDnm67moIfml7bpl7TngrnlnKjov5nkuKrlkajmnJ/lhoUNCiAgICAgICAgICAgICAgICByZW1haW5pbmdfaG91cnMgPSB0YXJnZXRfZWZmZWN0aXZlX2hvdXJzIC0gY3VtdWxhdGl2ZV9ob3Vycw0KICAgICAgICAgICAgICAgIHRhcmdldF90aW1lX3BvaW50ID0gcGVyaW9kWydzdGFydCddICsgdGltZWRlbHRhKGhvdXJzPXJlbWFpbmluZ19ob3VycykNCiAgICAgICAgICAgICAgICBicmVhaw0KICAgICAgICAgICAgZWxzZToNCiAgICAgICAgICAgICAgICBjdW11bGF0aXZlX2hvdXJzICs9IHBlcmlvZF9kdXJhdGlvbg0KICAgICAgICANCiAgICAgICAgZWZmZWN0aXZlX3RpbWVfcG9pbnRzW3Nsb3Rfc3RyXSA9IHRhcmdldF90aW1lX3BvaW50DQogICAgICAgIA0KICAgICAgICBpZiB0YXJnZXRfdGltZV9wb2ludDoNCiAgICAgICAgICAgIExPR0dFUi5pbmZvKCJTbG90ICVzOiBlZmZlY3RpdmUgJS4zZmgg4oaSIGFjdHVhbCB0aW1lICVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgc2xvdF9zdHIsIHRhcmdldF9lZmZlY3RpdmVfaG91cnMsIHRhcmdldF90aW1lX3BvaW50LnN0cmZ0aW1lKCclSDolTTolUycpKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgTE9HR0VSLndhcm5pbmcoIkNvdWxkIG5vdCBjYWxjdWxhdGUgZWZmZWN0aXZlIHRpbWUgcG9pbnQgZm9yIHNsb3QgJXMiLCBzbG90X3N0cikNCiAgICANCiAgICByZXR1cm4gZWZmZWN0aXZlX3RpbWVfcG9pbnRzDQoNCg0KZGVmIF9xdWVyeV9pbmZsdXhkYl9yYW5nZV93aXRoX2xvYWRfc3RhdHVzKA0KICAgIGZpZWxkX25hbWU6IHN0ciwNCiAgICBzdGFydF90aW1lOiBkYXRldGltZSwNCiAgICBlbmRfdGltZTogZGF0ZXRpbWUsDQogICAgaW5mbHV4X3VybDogc3RyLA0KICAgIGluZmx1eF9vcmc6IHN0ciwNCiAgICBpbmZsdXhfdG9rZW46IHN0ciwNCiAgICBpbmZsdXhfYnVja2V0OiBzdHIsDQogICAgaW5mbHV4X21lYXN1cmVtZW50OiBzdHIsDQogICAgZmlsdGVyczogT3B0aW9uYWxbRGljdFtzdHIsIHN0cl1dID0gTm9uZSwNCikgLT4gT3B0aW9uYWxbZmxvYXRdOg0KICAgICIiIuafpeivoiBJbmZsdXhEQiDojrflj5bmjIflrprlrZfmrrXlnKjml7bpl7TojIPlm7TlhoXnmoTlubPlnYflgLzvvIjku4XlvZMgbG9hZF9zdGF0dXMgPSAxIOaXtu+8iSIiIg0KICAgIHRyeToNCiAgICAgICAgZnJvbSBpbmZsdXhkYl9jbGllbnQgaW1wb3J0IEluZmx1eERCQ2xpZW50DQogICAgICAgIGltcG9ydCBwYW5kYXMgYXMgcGQNCiAgICAgICAgaW1wb3J0IHdhcm5pbmdzDQogICAgICAgIGZyb20gaW5mbHV4ZGJfY2xpZW50LmNsaWVudC53YXJuaW5ncyBpbXBvcnQgTWlzc2luZ1Bpdm90RnVuY3Rpb24NCiAgICBleGNlcHQgSW1wb3J0RXJyb3I6DQogICAgICAgIExPR0dFUi53YXJuaW5nKCJJbmZsdXhEQiBjbGllbnQgbm90IGF2YWlsYWJsZSwgc2tpcCBxdWVyeSBmb3IgZmllbGQ9JXMiLCBmaWVsZF9uYW1lKQ0KICAgICAgICByZXR1cm4gTm9uZQ0KDQogICAgdHJ5Og0KICAgICAgICBjbGllbnQgPSBJbmZsdXhEQkNsaWVudCh1cmw9aW5mbHV4X3VybCwgb3JnPWluZmx1eF9vcmcsIHRva2VuPWluZmx1eF90b2tlbikNCiAgICAgICAgcXVlcnlfYXBpID0gY2xpZW50LnF1ZXJ5X2FwaSgpDQoNCiAgICAgICAgc3RhcnRfcmZjID0gc3RhcnRfdGltZS5zdHJmdGltZSgnJVktJW0tJWRUJUg6JU06JVNaJykNCiAgICAgICAgZW5kX3JmYyA9IGVuZF90aW1lLnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKQ0KDQogICAgICAgICMg5p6E5bu66L+H5ruk5p2h5Lu2DQogICAgICAgIHRhZ19maWx0ZXJzID0gIiINCiAgICAgICAgaWYgZmlsdGVyczoNCiAgICAgICAgICAgIGZvciBrZXksIHZhbHVlIGluIGZpbHRlcnMuaXRlbXMoKToNCiAgICAgICAgICAgICAgICB0YWdfZmlsdGVycyArPSBmJ1xuICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJ7a2V5fSJdID09ICJ7dmFsdWV9IiknDQoNCiAgICAgICAgIyDlr7nkuo7njq/looPmuKnluqbvvIzlj5blhajpg6jpnZ4w5pWw5o2u55qE5Z2H5YC877yb5YW25LuW5a2X5q615LuN6ZyAbG9hZF9zdGF0dXM9Meetm+mAiQ0KICAgICAgICBpZiBmaWVsZF9uYW1lID09ICLnjq/looPmuKnluqYiOg0KICAgICAgICAgICAgZmx1eCA9IGYnJycNCmZyb20oYnVja2V0OiAie2luZmx1eF9idWNrZXR9IikNCiAgfD4gcmFuZ2Uoc3RhcnQ6IHtzdGFydF9yZmN9LCBzdG9wOiB7ZW5kX3JmY30pDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl9tZWFzdXJlbWVudCJdID09ICJ7aW5mbHV4X21lYXN1cmVtZW50fSIpDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl9maWVsZCJdID09ICJ7ZmllbGRfbmFtZX0iKQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJfdmFsdWUiXSAhPSAwLjApe3RhZ19maWx0ZXJzfQ0KICB8PiBtZWFuKCkNCiAgfD4geWllbGQobmFtZTogIm1lYW5fbm9uX3plcm8iKQ0KJycnLnN0cmlwKCkNCiAgICAgICAgZWxzZToNCiAgICAgICAgICAgIGZsdXggPSBmJycnDQpmcm9tKGJ1Y2tldDogIntpbmZsdXhfYnVja2V0fSIpDQogIHw+IHJhbmdlKHN0YXJ0OiB7c3RhcnRfcmZjfSwgc3RvcDoge2VuZF9yZmN9KQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJfbWVhc3VyZW1lbnQiXSA9PSAie2luZmx1eF9tZWFzdXJlbWVudH0iKQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJfZmllbGQiXSA9PSAie2ZpZWxkX25hbWV9Iil7dGFnX2ZpbHRlcnN9DQogIHw+IG1lYW4oKQ0KICB8PiB5aWVsZChuYW1lOiAibWVhbl90ZW1wZXJhdHVyZV9kYXRhIikNCicnJy5zdHJpcCgpDQoNCiAgICAgICAgTE9HR0VSLmRlYnVnKCJGbHV45p+l6K+i6K+t5Y+lIChyYW5nZSk6XG4lcyIsIGZsdXgpDQoNCiAgICAgICAgd2l0aCB3YXJuaW5ncy5jYXRjaF93YXJuaW5ncygpOg0KICAgICAgICAgICAgd2FybmluZ3Muc2ltcGxlZmlsdGVyKCJpZ25vcmUiLCBNaXNzaW5nUGl2b3RGdW5jdGlvbikNCiAgICAgICAgICAgIGZyYW1lcyA9IHF1ZXJ5X2FwaS5xdWVyeV9kYXRhX2ZyYW1lKGZsdXgpDQogICAgICAgIA0KICAgICAgICBpZiBpc2luc3RhbmNlKGZyYW1lcywgbGlzdCk6DQogICAgICAgICAgICBkZiA9IHBkLmNvbmNhdChmcmFtZXMsIGlnbm9yZV9pbmRleD1UcnVlKSBpZiBmcmFtZXMgZWxzZSBwZC5EYXRhRnJhbWUoKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgZGYgPSBmcmFtZXMNCg0KICAgICAgICBpZiBkZi5lbXB0eSBvciAnX3ZhbHVlJyBub3QgaW4gZGYuY29sdW1uczoNCiAgICAgICAgICAgIGlmIGZpZWxkX25hbWUgPT0gIueOr+Wig+a4qeW6piI6DQogICAgICAgICAgICAgICAgTE9HR0VSLmRlYnVnKCJObyB2YWxpZCByYW5nZSBkYXRhIGZvdW5kIGZvciBmaWVsZD0lcyAobm9uLXplcm8gZGF0YSkiLCBmaWVsZF9uYW1lKQ0KICAgICAgICAgICAgZWxzZToNCiAgICAgICAgICAgICAgICBMT0dHRVIuZGVidWcoIk5vIHZhbGlkIHJhbmdlIGRhdGEgZm91bmQgZm9yIGZpZWxkPSVzIiwgZmllbGRfbmFtZSkNCiAgICAgICAgICAgIHJldHVybiBOb25lDQogICAgICAgICAgICANCiAgICAgICAgbWVhbl92YWx1ZSA9IGRmWydfdmFsdWUnXS5pbG9jWzBdDQogICAgICAgIGlmIHBkLmlzbmEobWVhbl92YWx1ZSk6DQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIk1lYW4gdmFsdWUgaXMgTmFOIGZvciBmaWVsZD0lcyIsIGZpZWxkX25hbWUpDQogICAgICAgICAgICByZXR1cm4gTm9uZQ0KDQogICAgICAgIHZhbHVlID0gZmxvYXQobWVhbl92YWx1ZSkNCiAgICAgICAgaWYgZmllbGRfbmFtZSA9PSAi546v5aKD5rip5bqmIjoNCiAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygiRmllbGQ9JXMgcmFuZ2VfbWVhbl92YWx1ZT0lLjNmIChub24temVybyBkYXRhKSIsIGZpZWxkX25hbWUsIHZhbHVlKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgTE9HR0VSLmRlYnVnKCJGaWVsZD0lcyByYW5nZV9tZWFuX3ZhbHVlPSUuM2YiLCBmaWVsZF9uYW1lLCB2YWx1ZSkNCiAgICAgICAgcmV0dXJuIHZhbHVlDQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOg0KICAgICAgICBMT0dHRVIuZXJyb3IoIkVycm9yIHF1ZXJ5aW5nIEluZmx1eERCIHJhbmdlIGZvciBmaWVsZD0lczogJXMiLCBmaWVsZF9uYW1lLCBlKQ0KICAgICAgICByZXR1cm4gTm9uZQ0KICAgIGZpbmFsbHk6DQogICAgICAgIHRyeToNCiAgICAgICAgICAgIGNsaWVudC5jbG9zZSgpDQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb246DQogICAgICAgICAgICBwYXNzDQoNCg0KZGVmIF9xdWVyeV9pbmZsdXhkYl93aXRoX2xvYWRfc3RhdHVzKA0KICAgIGZpZWxkX25hbWU6IHN0ciwNCiAgICB0YXJnZXRfdGltZTogZGF0ZXRpbWUsDQogICAgaW5mbHV4X3VybDogc3RyLA0KICAgIGluZmx1eF9vcmc6IHN0ciwNCiAgICBpbmZsdXhfdG9rZW46IHN0ciwNCiAgICBpbmZsdXhfYnVja2V0OiBzdHIsDQogICAgaW5mbHV4X21lYXN1cmVtZW50OiBzdHIsDQogICAgZmlsdGVyczogT3B0aW9uYWxbRGljdFtzdHIsIHN0cl1dID0gTm9uZSwNCikgLT4gT3B0aW9uYWxbZmxvYXRdOg0KICAgICIiIuafpeivoiBJbmZsdXhEQiDojrflj5bmjIflrprlrZfmrrXlnKjmjIflrprml7bpl7TngrnnmoTnnqzml7blgLzvvIjku4XlvZMgbG9hZF9zdGF0dXMgPSAxIOaXtu+8iSIiIg0KICAgIHRyeToNCiAgICAgICAgZnJvbSBpbmZsdXhkYl9jbGllbnQgaW1wb3J0IEluZmx1eERCQ2xpZW50DQogICAgICAgIGltcG9ydCBwYW5kYXMgYXMgcGQNCiAgICAgICAgaW1wb3J0IHdhcm5pbmdzDQogICAgICAgIGZyb20gaW5mbHV4ZGJfY2xpZW50LmNsaWVudC53YXJuaW5ncyBpbXBvcnQgTWlzc2luZ1Bpdm90RnVuY3Rpb24NCiAgICBleGNlcHQgSW1wb3J0RXJyb3I6DQogICAgICAgIExPR0dFUi53YXJuaW5nKCJJbmZsdXhEQiBjbGllbnQgbm90IGF2YWlsYWJsZSwgc2tpcCBxdWVyeSBmb3IgZmllbGQ9JXMiLCBmaWVsZF9uYW1lKQ0KICAgICAgICByZXR1cm4gTm9uZQ0KDQogICAgdHJ5Og0KICAgICAgICBjbGllbnQgPSBJbmZsdXhEQkNsaWVudCh1cmw9aW5mbHV4X3VybCwgb3JnPWluZmx1eF9vcmcsIHRva2VuPWluZmx1eF90b2tlbikNCiAgICAgICAgcXVlcnlfYXBpID0gY2xpZW50LnF1ZXJ5X2FwaSgpDQoNCiAgICAgICAgTE9HR0VSLmRlYnVnKA0KICAgICAgICAgICAgIlF1ZXJ5aW5nIGZpZWxkPSVzIG1lYXN1cmVtZW50PSVzIHRhcmdldF90aW1lPSVzIGZpbHRlcnM9JXMgKHdpdGggbG9hZF9zdGF0dXM9MSkiLA0KICAgICAgICAgICAgZmllbGRfbmFtZSwNCiAgICAgICAgICAgIGluZmx1eF9tZWFzdXJlbWVudCwNCiAgICAgICAgICAgIHRhcmdldF90aW1lLnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKSwNCiAgICAgICAgICAgIGZpbHRlcnMgb3Ige30sDQogICAgICAgICkNCg0KICAgICAgICAjIOafpeivoumAu+i+ke+8muafpeivouebruagh+aXtumXtOeCuemZhOi/keeahOaVsOaNru+8jOS9huWPquimgSBsb2FkX3N0YXR1cyA9IDEg55qE5pWw5o2uDQogICAgICAgICMg5L2/55So5LiA5Liq5pe26Ze056qX5Y+j5p2l5p+l5om+5pyA5o6l6L+R55qE5pyJ5pWI5pWw5o2u54K5DQogICAgICAgIHdpbmRvd19taW51dGVzID0gMTAgICMg5YmN5ZCOMTDliIbpkp/nmoTnqpflj6MNCiAgICAgICAgDQogICAgICAgIHF1ZXJ5X3N0YXJ0ID0gdGFyZ2V0X3RpbWUgLSB0aW1lZGVsdGEobWludXRlcz13aW5kb3dfbWludXRlcykNCiAgICAgICAgcXVlcnlfZW5kID0gdGFyZ2V0X3RpbWUgKyB0aW1lZGVsdGEobWludXRlcz13aW5kb3dfbWludXRlcykNCiAgICAgICAgDQogICAgICAgIHF1ZXJ5X3N0YXJ0X3JmYyA9IHF1ZXJ5X3N0YXJ0LnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKQ0KICAgICAgICBxdWVyeV9lbmRfcmZjID0gcXVlcnlfZW5kLnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKQ0KDQogICAgICAgICMg5p6E5bu66L+H5ruk5p2h5Lu2DQogICAgICAgIHRhZ19maWx0ZXJzID0gIiINCiAgICAgICAgaWYgZmlsdGVyczoNCiAgICAgICAgICAgIGZvciBrZXksIHZhbHVlIGluIGZpbHRlcnMuaXRlbXMoKToNCiAgICAgICAgICAgICAgICB0YWdfZmlsdGVycyArPSBmJ1xuICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJ7a2V5fSJdID09ICJ7dmFsdWV9IiknDQoNCiAgICAgICAgIyDmn6Xor6LmuKnluqbmlbDmja7vvIjkuI3pnIDopoFsb2FkX3N0YXR1c+etm+mAie+8jOWboOS4uuW3sue7j+WfuuS6juacieaViOaXtumXtOeCueafpeivou+8iQ0KICAgICAgICBmbHV4ID0gZicnJw0KZnJvbShidWNrZXQ6ICJ7aW5mbHV4X2J1Y2tldH0iKQ0KICB8PiByYW5nZShzdGFydDoge3F1ZXJ5X3N0YXJ0X3JmY30sIHN0b3A6IHtxdWVyeV9lbmRfcmZjfSkNCiAgfD4gZmlsdGVyKGZuOiAocikgPT4gclsiX21lYXN1cmVtZW50Il0gPT0gIntpbmZsdXhfbWVhc3VyZW1lbnR9IikNCiAgfD4gZmlsdGVyKGZuOiAocikgPT4gclsiX2ZpZWxkIl0gPT0gIntmaWVsZF9uYW1lfSIpe3RhZ19maWx0ZXJzfQ0KICB8PiBzb3J0KGNvbHVtbnM6IFsiX3RpbWUiXSkNCiAgfD4gbGFzdCgpDQogIHw+IHlpZWxkKG5hbWU6ICJpbnN0YW50YW5lb3VzX2F0X2VmZmVjdGl2ZV90aW1lIikNCicnJy5zdHJpcCgpDQoNCiAgICAgICAgTE9HR0VSLmRlYnVnKCJGbHV45p+l6K+i6K+t5Y+lOlxuJXMiLCBmbHV4KQ0KDQogICAgICAgIHdpdGggd2FybmluZ3MuY2F0Y2hfd2FybmluZ3MoKToNCiAgICAgICAgICAgIHdhcm5pbmdzLnNpbXBsZWZpbHRlcigiaWdub3JlIiwgTWlzc2luZ1Bpdm90RnVuY3Rpb24pDQogICAgICAgICAgICBmcmFtZXMgPSBxdWVyeV9hcGkucXVlcnlfZGF0YV9mcmFtZShmbHV4KQ0KICAgICAgICANCiAgICAgICAgaWYgaXNpbnN0YW5jZShmcmFtZXMsIGxpc3QpOg0KICAgICAgICAgICAgZGYgPSBwZC5jb25jYXQoZnJhbWVzLCBpZ25vcmVfaW5kZXg9VHJ1ZSkgaWYgZnJhbWVzIGVsc2UgcGQuRGF0YUZyYW1lKCkNCiAgICAgICAgZWxzZToNCiAgICAgICAgICAgIGRmID0gZnJhbWVzDQoNCiAgICAgICAgIyDojrflj5bnnqzml7blgLzvvIjmnIDov5HnmoTkuIDkuKrmnInmlYjmlbDmja7ngrnvvIkNCiAgICAgICAgaWYgZGYuZW1wdHkgb3IgJ192YWx1ZScgbm90IGluIGRmLmNvbHVtbnM6DQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIk5vIHZhbGlkIGRhdGEgZm91bmQgZm9yIGZpZWxkPSVzIGF0IGVmZmVjdGl2ZSB0aW1lIHBvaW50IiwgZmllbGRfbmFtZSkNCiAgICAgICAgICAgIHJldHVybiBOb25lDQogICAgICAgICAgICANCiAgICAgICAgIyDlj5bnrKzkuIDooYznmoTlgLzvvIjlm6DkuLrmn6Xor6Llt7Lnu4/mjpLluo/lubblj5bkuoZsYXN0KCnvvIkNCiAgICAgICAgaW5zdGFudF92YWx1ZSA9IGRmWydfdmFsdWUnXS5pbG9jWzBdDQogICAgICAgIGlmIHBkLmlzbmEoaW5zdGFudF92YWx1ZSk6DQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIkluc3RhbnRhbmVvdXMgdmFsdWUgaXMgTmFOIGZvciBmaWVsZD0lcyIsIGZpZWxkX25hbWUpDQogICAgICAgICAgICByZXR1cm4gTm9uZQ0KDQogICAgICAgIHZhbHVlID0gZmxvYXQoaW5zdGFudF92YWx1ZSkNCiAgICAgICAgDQogICAgICAgICMg5aaC5p6c5pyJ5pe26Ze05L+h5oGv77yM6K6w5b2V5a6e6ZmF55qE5pWw5o2u5pe26Ze054K5DQogICAgICAgIGlmICdfdGltZScgaW4gZGYuY29sdW1uczoNCiAgICAgICAgICAgIGFjdHVhbF90aW1lID0gZGZbJ190aW1lJ10uaWxvY1swXQ0KICAgICAgICAgICAgTE9HR0VSLmRlYnVnKCJGaWVsZD0lcyBpbnN0YW50YW5lb3VzX3ZhbHVlPSUuM2YgYWN0dWFsX3RpbWU9JXMgKGF0IGVmZmVjdGl2ZSB0aW1lKSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgZmllbGRfbmFtZSwgdmFsdWUsIGFjdHVhbF90aW1lKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgTE9HR0VSLmRlYnVnKCJGaWVsZD0lcyBpbnN0YW50YW5lb3VzX3ZhbHVlPSUuM2YgKGF0IGVmZmVjdGl2ZSB0aW1lKSIsIGZpZWxkX25hbWUsIHZhbHVlKQ0KICAgICAgICAgICAgDQogICAgICAgIHJldHVybiB2YWx1ZQ0KICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToNCiAgICAgICAgTE9HR0VSLmVycm9yKCJFcnJvciBxdWVyeWluZyBJbmZsdXhEQiBmb3IgZmllbGQ9JXM6ICVzIiwgZmllbGRfbmFtZSwgZSkNCiAgICAgICAgcmV0dXJuIE5vbmUNCiAgICBmaW5hbGx5Og0KICAgICAgICB0cnk6DQogICAgICAgICAgICBjbGllbnQuY2xvc2UoKQ0KICAgICAgICBleGNlcHQgRXhjZXB0aW9uOg0KICAgICAgICAgICAgcGFzcw0KDQoNCmRlZiBfbG9hZF90ZW1wZXJhdHVyZV9kYXRhX3dpdGhfbG9hZF9zdGF0dXMoDQogICAgdGltZV9zbG90czogTGlzdFtzdHJdLA0KICAgIHNlY3Rpb25zOiBMaXN0W0RpY3Rbc3RyLCBBbnldXSwNCiAgICBzdGFydF90aW1lOiBPcHRpb25hbFtkYXRldGltZV0sDQogICAgZW5kX3RpbWU6IE9wdGlvbmFsW2RhdGV0aW1lXSwNCikgLT4gRGljdFtzdHIsIERpY3Rbc3RyLCBmbG9hdF1dOg0KICAgICIiIuS7jiBJbmZsdXhEQiDmn6Xor6LmiYDmnInmtYvor5Xpg6jkvY3lnKjlkITml7bpl7TngrnnmoTnnqzml7bmuKnluqblgLzvvIjku4XlvZMgbG9hZF9zdGF0dXMgPSAxIOaXtu+8iSIiIg0KICAgIGlmIG5vdCBzdGFydF90aW1lIG9yIG5vdCBlbmRfdGltZToNCiAgICAgICAgTE9HR0VSLmluZm8oIlNraXAgZGF0YSBxdWVyeTogbWlzc2luZyBzdGFydC9lbmQgKCVzLCAlcykiLCBzdGFydF90aW1lLCBlbmRfdGltZSkNCiAgICAgICAgcmV0dXJuIHt9DQogICAgDQogICAgaW5mbHV4X2NvbmZpZyA9IF9nZXRfaW5mbHV4X2NvbmZpZygpDQogICAgDQogICAgaWYgbm90IGFsbChbaW5mbHV4X2NvbmZpZ1sndXJsJ10sIGluZmx1eF9jb25maWdbJ29yZyddLCBpbmZsdXhfY29uZmlnWyd0b2tlbiddLCANCiAgICAgICAgICAgICAgICBpbmZsdXhfY29uZmlnWydidWNrZXQnXSwgaW5mbHV4X2NvbmZpZ1snbWVhc3VyZW1lbnQnXV0pOg0KICAgICAgICBMT0dHRVIud2FybmluZygNCiAgICAgICAgICAgICJTa2lwIGRhdGEgcXVlcnk6IG1pc3NpbmcgSW5mbHV4IGNvbmZpZyB1cmw9JXMgYnVja2V0PSVzIG1lYXN1cmVtZW50PSVzIiwNCiAgICAgICAgICAgIGluZmx1eF9jb25maWdbJ3VybCddIG9yICI8ZW1wdHk+IiwNCiAgICAgICAgICAgIGluZmx1eF9jb25maWdbJ2J1Y2tldCddIG9yICI8ZW1wdHk+IiwNCiAgICAgICAgICAgIGluZmx1eF9jb25maWdbJ21lYXN1cmVtZW50J10gb3IgIjxlbXB0eT4iLA0KICAgICAgICApDQogICAgICAgIHJldHVybiB7fQ0KICAgIA0KICAgICMg6K6h566X5oC75pe26ZW/77yI5bCP5pe277yJDQogICAgdG90YWxfZHVyYXRpb24gPSAoZW5kX3RpbWUgLSBzdGFydF90aW1lKS50b3RhbF9zZWNvbmRzKCkgLyAzNjAwLjANCiAgICBMT0dHRVIuaW5mbygNCiAgICAgICAgIkZldGNoIGluc3RhbnRhbmVvdXMgdGVtcGVyYXR1cmUgZGF0YSAobG9hZF9zdGF0dXM9MSkgd2luZG93PSVz4oaSJXMgdG90YWxfaG91cnM9JS4zZiB0aW1lX3BvaW50cz0lcyIsDQogICAgICAgIHN0YXJ0X3RpbWUuaXNvZm9ybWF0KCksDQogICAgICAgIGVuZF90aW1lLmlzb2Zvcm1hdCgpLA0KICAgICAgICB0b3RhbF9kdXJhdGlvbiwNCiAgICAgICAgIiwiLmpvaW4odGltZV9zbG90cyksDQogICAgKQ0KICAgIA0KICAgICMg5pS26ZuG5omA5pyJ6ZyA6KaB5p+l6K+i55qE5a2X5q61DQogICAgcXVlcnlfdGFyZ2V0czogTGlzdFt0dXBsZVtzdHIsIERpY3Rbc3RyLCBBbnldXV0gPSBbXQ0KICAgIGZvciBzZWN0aW9uIGluIHNlY3Rpb25zOg0KICAgICAgICBlbnRyaWVzID0gc2VjdGlvbi5nZXQoImVudHJpZXMiKSBvciBbXQ0KICAgICAgICBmb3IgZW50cnkgaW4gZW50cmllczoNCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZW50cnksIGRpY3QpOg0KICAgICAgICAgICAgICAgIGZpZWxkX25hbWUgPSBlbnRyeS5nZXQoImZpZWxkIiwgIiIpDQogICAgICAgICAgICAgICAgaWYgZmllbGRfbmFtZToNCiAgICAgICAgICAgICAgICAgICAgcXVlcnlfdGFyZ2V0cy5hcHBlbmQoKGZpZWxkX25hbWUsIGVudHJ5KSkNCg0KICAgIGlmIG5vdCBxdWVyeV90YXJnZXRzOg0KICAgICAgICByZXR1cm4ge30NCiAgICANCiAgICAjIOiuoeeul+WfuuS6juacieaViOi/kOihjOaXtumXtOe0r+iuoeeahOecn+WunuaXtumXtOeCuQ0KICAgIExPR0dFUi5pbmZvKCI9PT0g5byA5aeL6K6h566X5pyJ5pWI5pe26Ze054K5ID09PSIpDQogICAgZWZmZWN0aXZlX3RpbWVfcG9pbnRzID0gX2NhbGN1bGF0ZV9lZmZlY3RpdmVfdGltZV9wb2ludHMoDQogICAgICAgIHN0YXJ0X3RpbWUsIGVuZF90aW1lLCB0aW1lX3Nsb3RzLCBpbmZsdXhfY29uZmlnDQogICAgKQ0KICAgIA0KICAgICMg5Li65q+P5Liq5pyJ5pWI5pe26Ze054K55p+l6K+i5rip5bqm5pWw5o2uDQogICAgdGVtcGVyYXR1cmVfZGF0YTogRGljdFtzdHIsIERpY3Rbc3RyLCBmbG9hdF1dID0ge30NCiAgICANCiAgICBmb3IgaWR4LCBzbG90X3N0ciBpbiBlbnVtZXJhdGUodGltZV9zbG90cyk6DQogICAgICAgIHRhcmdldF90aW1lX3BvaW50ID0gZWZmZWN0aXZlX3RpbWVfcG9pbnRzLmdldChzbG90X3N0cikNCiAgICAgICAgDQogICAgICAgIGlmIHRhcmdldF90aW1lX3BvaW50IGlzIE5vbmU6DQogICAgICAgICAgICBMT0dHRVIud2FybmluZygiTm8gZWZmZWN0aXZlIHRpbWUgcG9pbnQgY2FsY3VsYXRlZCBmb3Igc2xvdCAlcywgc2tpcHBpbmciLCBzbG90X3N0cikNCiAgICAgICAgICAgIGNvbnRpbnVlDQogICAgICAgIA0KICAgICAgICBMT0dHRVIuZGVidWcoIlByb2Nlc3Npbmcgc2xvdCAlcyBhdCBlZmZlY3RpdmUgdGltZSBwb2ludCAlcyIsIA0KICAgICAgICAgICAgICAgICAgICBzbG90X3N0ciwgdGFyZ2V0X3RpbWVfcG9pbnQuc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJykpDQogICAgICAgIA0KICAgICAgICBmb3IgZmllbGRfbmFtZSwgZW50cnkgaW4gcXVlcnlfdGFyZ2V0czoNCiAgICAgICAgICAgIHJlc3VsdF9rZXkgPSBlbnRyeS5nZXQoInJlc3VsdF9rZXkiKSBvciBmaWVsZF9uYW1lDQogICAgICAgICAgICBpZiBub3QgcmVzdWx0X2tleToNCiAgICAgICAgICAgICAgICByZXN1bHRfa2V5ID0gZmllbGRfbmFtZQ0KICAgICAgICAgICAgZW50cnlfZmlsdGVycyA9IGVudHJ5LmdldCgiZmlsdGVycyIpIGlmIGlzaW5zdGFuY2UoZW50cnksIGRpY3QpIGVsc2UgTm9uZQ0KICAgICAgICAgICAgaWYgcmVzdWx0X2tleSBub3QgaW4gdGVtcGVyYXR1cmVfZGF0YToNCiAgICAgICAgICAgICAgICB0ZW1wZXJhdHVyZV9kYXRhW3Jlc3VsdF9rZXldID0ge30NCg0KICAgICAgICAgICAgIyDkvb/nlKjntKLlvJXkvZzkuLprZXnvvIzlm6DkuLrlj6/og73mnInph43lpI3nmoTml7bpl7TliLvluqYNCiAgICAgICAgICAgIHNsb3Rfa2V5ID0gZiJ7aWR4fV97c2xvdF9zdHJ9IiAgIyDkvb/nlKjntKLlvJUr5pe26Ze05Yi75bqm5L2c5Li65ZSv5LiAa2V5DQoNCiAgICAgICAgICAgICMg5p+l6K+i556s5pe25YC877yI5Zyo5pyJ5pWI5pe26Ze054K577yJDQogICAgICAgICAgICB2YWx1ZSA9IF9xdWVyeV9pbmZsdXhkYl93aXRoX2xvYWRfc3RhdHVzKA0KICAgICAgICAgICAgICAgIGZpZWxkX25hbWUsDQogICAgICAgICAgICAgICAgdGFyZ2V0X3RpbWVfcG9pbnQsDQogICAgICAgICAgICAgICAgaW5mbHV4X2NvbmZpZ1sndXJsJ10sDQogICAgICAgICAgICAgICAgaW5mbHV4X2NvbmZpZ1snb3JnJ10sDQogICAgICAgICAgICAgICAgaW5mbHV4X2NvbmZpZ1sndG9rZW4nXSwNCiAgICAgICAgICAgICAgICBpbmZsdXhfY29uZmlnWydidWNrZXQnXSwNCiAgICAgICAgICAgICAgICBpbmZsdXhfY29uZmlnWydtZWFzdXJlbWVudCddLA0KICAgICAgICAgICAgICAgIGZpbHRlcnM9ZW50cnlfZmlsdGVycyBpZiBlbnRyeV9maWx0ZXJzIGVsc2UgTm9uZSwNCiAgICAgICAgICAgICkNCg0KICAgICAgICAgICAgaWYgdmFsdWUgaXMgbm90IE5vbmU6DQogICAgICAgICAgICAgICAgdGVtcGVyYXR1cmVfZGF0YVtyZXN1bHRfa2V5XVtzbG90X2tleV0gPSB2YWx1ZQ0KICAgICAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygNCiAgICAgICAgICAgICAgICAgICAgIlNsb3Q9JXMgZmllbGQ9JXMgdmFsdWU9JS4zZiBhdCBlZmZlY3RpdmVfdGltZT0lcyIsDQogICAgICAgICAgICAgICAgICAgIHNsb3Rfa2V5LA0KICAgICAgICAgICAgICAgICAgICByZXN1bHRfa2V5LA0KICAgICAgICAgICAgICAgICAgICB2YWx1ZSwNCiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X3RpbWVfcG9pbnQuc3RyZnRpbWUoJyVIOiVNOiVTJykNCiAgICAgICAgICAgICAgICApDQogICAgICAgICAgICBlbHNlOg0KICAgICAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygNCiAgICAgICAgICAgICAgICAgICAgIlNsb3Q9JXMgZmllbGQ9JXMgbm9fZGF0YSBhdCBlZmZlY3RpdmVfdGltZT0lcyIsDQogICAgICAgICAgICAgICAgICAgIHNsb3Rfa2V5LA0KICAgICAgICAgICAgICAgICAgICByZXN1bHRfa2V5LA0KICAgICAgICAgICAgICAgICAgICB0YXJnZXRfdGltZV9wb2ludC5zdHJmdGltZSgnJUg6JU06JVMnKQ0KICAgICAgICAgICAgICAgICkNCg0KICAgIHJldHVybiB0ZW1wZXJhdHVyZV9kYXRhDQoNCg0KZGVmIF9idWlsZF9jZWxsc193aXRoX2xvYWRfc3RhdHVzKA0KICAgIHRpbWVfc2xvdHM6IExpc3Rbc3RyXSwNCiAgICBzZWN0aW9uczogTGlzdFtEaWN0W3N0ciwgQW55XV0sDQogICAgbW90b3Jfc3BlZWQ6IHN0ciwNCiAgICBzdGFydF90aW1lOiBPcHRpb25hbFtkYXRldGltZV0sDQogICAgZW5kX3RpbWU6IE9wdGlvbmFsW2RhdGV0aW1lXSwNCiAgICB0ZW1wZXJhdHVyZV9kYXRhOiBEaWN0W3N0ciwgRGljdFtzdHIsIGZsb2F0XV0sDQogICAgdXNlX2RlZmF1bHRzOiBib29sID0gRmFsc2UsDQopIC0+IExpc3RbRGljdFtzdHIsIEFueV1dOg0KICAgICIiIuaehOW7uuWNleWFg+agvOaVsOaNru+8iOWfuuS6jiBsb2FkX3N0YXR1cyA9IDEg55qE5pyJ5pWI5pWw5o2u77yJLSDkuI7ljp/lp4vohJrmnKznu5PmnoTlrozlhajkuIDoh7QiIiINCiAgICBjZWxsczogTGlzdFtEaWN0W3N0ciwgQW55XV0gPSBbXQ0KDQogICAgZGVmIGFkZF9jZWxsKHJvdzogaW50LCBjb2w6IGludCwgdmFsdWU6IHN0ciA9ICIiLCByb3dzcGFuOiBpbnQgPSAxLCBjb2xzcGFuOiBpbnQgPSAxKSAtPiBOb25lOg0KICAgICAgICBwYXlsb2FkOiBEaWN0W3N0ciwgQW55XSA9IHsicm93Ijogcm93LCAiY29sIjogY29sLCAidmFsdWUiOiB2YWx1ZX0NCiAgICAgICAgaWYgcm93c3BhbiA+IDE6DQogICAgICAgICAgICBwYXlsb2FkWyJyb3dzcGFuIl0gPSByb3dzcGFuDQogICAgICAgIGlmIGNvbHNwYW4gPiAxOg0KICAgICAgICAgICAgcGF5bG9hZFsiY29sc3BhbiJdID0gY29sc3Bhbg0KICAgICAgICBjZWxscy5hcHBlbmQocGF5bG9hZCkNCg0KICAgICMg5qih5p2/5bem5L6n5qCH6aKY5YiX5bey57uP5Y676Zmk77yM6L+Z6YeM5LuF55Sf5oiQ57qv5pWw5o2u5Yy677yM5LuOICgwLDApIOW8gOWni+Whq+WFpeaVsOWAvOOAgg0KICAgICMgY3VycmVudF9yb3cg5a+55bqU5qih5p2/5Lit55qE5a6e6ZmF5pWw5o2u6KGM57Si5byV44CCDQogICAgY3VycmVudF9yb3cgPSAwDQogICAgZm9yIHNlY3Rpb24gaW4gc2VjdGlvbnM6DQogICAgICAgIGVudHJpZXMgPSBzZWN0aW9uLmdldCgiZW50cmllcyIpIG9yIFtdDQogICAgICAgIGlmIG5vdCBlbnRyaWVzOg0KICAgICAgICAgICAgY29udGludWUNCiAgICAgICAgIyDmr4/kuKrmtYvor5Xpg6jkvY3lrZDpobnlr7nlupTmqKHmnb/kuK3nmoTkuIDooYwNCiAgICAgICAgZm9yIGVudHJ5IGluIGVudHJpZXM6DQogICAgICAgICAgICAjIOaUr+aMgeaWsOagvOW8j++8iOW4piBmaWVsZCDmmKDlsITvvInlkozml6fmoLzlvI/vvIjnuq/lrZfnrKbkuLLvvIkNCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZW50cnksIGRpY3QpOg0KICAgICAgICAgICAgICAgIGZpZWxkX25hbWUgPSBlbnRyeS5nZXQoImZpZWxkIiwgIiIpDQogICAgICAgICAgICAgICAgZW50cnlfZmlsdGVycyA9IGVudHJ5LmdldCgiZmlsdGVycyIpDQogICAgICAgICAgICAgICAgZW50cnlfa2V5ID0gZW50cnkuZ2V0KCJyZXN1bHRfa2V5Iikgb3IgZmllbGRfbmFtZQ0KICAgICAgICAgICAgZWxzZToNCiAgICAgICAgICAgICAgICBmaWVsZF9uYW1lID0gIiINCiAgICAgICAgICAgICAgICBlbnRyeV9maWx0ZXJzID0gTm9uZQ0KICAgICAgICAgICAgICAgIGVudHJ5X2tleSA9ICIiDQoNCiAgICAgICAgICAgICMg5LuF6L6T5Ye65pWw5YC85YiX77ya5YiX57Si5byV55u05o6l5a+55bqU5pe26Ze05q61DQogICAgICAgICAgICAjIOW8uuWItuWhq+WFheaJgOacieWIl++8jOS8mOWFiOS9v+eUqOafpeivouaVsOaNru+8jOWQpuWImeS9v+eUqOm7mOiupOWAvA0KICAgICAgICAgICAgaWYgZmllbGRfbmFtZToNCiAgICAgICAgICAgICAgICB0YXJnZXRfa2V5ID0gZW50cnlfa2V5IG9yIGZpZWxkX25hbWUNCg0KICAgICAgICAgICAgICAgICMg6YGN5Y6G5omA5pyJ5pe26Ze05q615YiX77yM56Gu5L+d5q+P5LiA5YiX6YO95pyJ5pWw5o2uDQogICAgICAgICAgICAgICAgZm9yIGNvbF9pZHgsIHNsb3QgaW4gZW51bWVyYXRlKHRpbWVfc2xvdHMpOg0KICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IE5vbmUNCg0KICAgICAgICAgICAgICAgICAgICAjIOS8mOWFiOS9v+eUqOafpeivouWIsOeahOaVsOaNrg0KICAgICAgICAgICAgICAgICAgICBpZiB0ZW1wZXJhdHVyZV9kYXRhOg0KICAgICAgICAgICAgICAgICAgICAgICAgc2xvdF9kYXRhID0gdGVtcGVyYXR1cmVfZGF0YS5nZXQodGFyZ2V0X2tleSwge30pDQogICAgICAgICAgICAgICAgICAgICAgICBpZiBzbG90X2RhdGE6DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvdF9rZXkgPSBmIntjb2xfaWR4fV97c2xvdH0iDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBzbG90X2RhdGEuZ2V0KHNsb3Rfa2V5KQ0KDQogICAgICAgICAgICAgICAgICAgIGlmIHZhbHVlIGlzIE5vbmUgYW5kIHVzZV9kZWZhdWx0czoNCiAgICAgICAgICAgICAgICAgICAgICAgICMg5L2/55So5Z+656GA6buY6K6k5YC8ICsg5pe26Ze05q615YGP56e777yI5q+P5Liq5pe26Ze05q615aKe5YqgMC4x5bqm77yJDQogICAgICAgICAgICAgICAgICAgICAgICBkZWZhdWx0X2Jhc2VfdmFsdWUgPSAyNS4wICAjIOeugOWMlueahOm7mOiupOWAvA0KICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9vZmZzZXQgPSBjb2xfaWR4ICogMC4xDQogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGRlZmF1bHRfYmFzZV92YWx1ZSArIHRpbWVfb2Zmc2V0DQoNCiAgICAgICAgICAgICAgICAgICAgaWYgdmFsdWUgaXMgTm9uZToNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlX3N0ciA9ICIiDQogICAgICAgICAgICAgICAgICAgIGVsc2U6DQogICAgICAgICAgICAgICAgICAgICAgICAjIOagvOW8j+WMluS4uuWtl+espuS4su+8iOS/neeVmTHkvY3lsI/mlbDvvIkNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlX3N0ciA9IGYie3ZhbHVlOi4xZn0iDQoNCiAgICAgICAgICAgICAgICAgICAgYWRkX2NlbGwoY3VycmVudF9yb3csIGNvbF9pZHgsIHZhbHVlX3N0cikNCiAgICAgICAgICAgIGVsc2U6DQogICAgICAgICAgICAgICAgIyDlpoLmnpzmsqHmnInlrZfmrrXlkI3vvIzloavlhYXnqbrlrZfnrKbkuLINCiAgICAgICAgICAgICAgICBmb3IgY29sX2lkeCBpbiByYW5nZShsZW4odGltZV9zbG90cykpOg0KICAgICAgICAgICAgICAgICAgICBhZGRfY2VsbChjdXJyZW50X3JvdywgY29sX2lkeCwgIiIpDQogICAgICAgICAgICBjdXJyZW50X3JvdyArPSAxDQoNCiAgICByZXR1cm4gY2VsbHMNCg0KDQpkZWYgYnVpbGRfdGVtcGVyYXR1cmVfdGFibGVfd2l0aF9sb2FkX3N0YXR1cyhfOiBEaWN0W3N0ciwgQW55XSkgLT4gRGljdFtzdHIsIEFueV06DQogICAgIiIi5p6E5bu65rip5bqm6KGo5qC85pWw5o2u77yI5LuF5L2/55SoIGxvYWRfc3RhdHVzID0gMSDnmoTmnInmlYjmlbDmja7vvIkiIiINCiAgICBfc2V0dXBfbG9nZ2luZygpDQogICAgDQogICAgdG9rZW4gPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfVE9LRU4iLCAic2NyaXB0VGFibGUxIikNCiAgICByb3dfb2Zmc2V0ID0gaW50KG9zLmVudmlyb24uZ2V0KCJUQUJMRV9TVEFSVF9ST1ciLCAiMCIpIG9yIDApDQogICAgY29sX29mZnNldCA9IGludChvcy5lbnZpcm9uLmdldCgiVEFCTEVfU1RBUlRfQ09MIiwgIjAiKSBvciAwKQ0KICAgIG1vdG9yX3NwZWVkID0gb3MuZW52aXJvbi5nZXQoIlRBQkxFX01PVE9SX1NQRUVEIiwgIjk4MFJQTSIpDQogICAgDQogICAgIyDop6PmnpDlrp7pqozml7bpl7TojIPlm7QNCiAgICBzdGFydF90aW1lLCBlbmRfdGltZSA9IF9wYXJzZV9leHBlcmltZW50X3RpbWVzKCkNCiAgICANCiAgICB0aW1lX3Nsb3RzID0gX3RpbWVfc2xvdHMoKQ0KICAgIHNlY3Rpb25zID0gX2RlZmF1bHRfc2VjdGlvbnMoKQ0KICAgIA0KICAgICMg5p+l6K+i5rip5bqm5pWw5o2u77yI5LuF5b2TIGxvYWRfc3RhdHVzID0gMSDml7bvvIkNCiAgICB0ZW1wZXJhdHVyZV9kYXRhID0gX2xvYWRfdGVtcGVyYXR1cmVfZGF0YV93aXRoX2xvYWRfc3RhdHVzKHRpbWVfc2xvdHMsIHNlY3Rpb25zLCBzdGFydF90aW1lLCBlbmRfdGltZSkNCiAgICANCiAgICAjIOWni+e7iOemgeatoum7mOiupOaVsOaNru+8jOS/neivgeafpeivouS4jeWIsOWAvOaXtuS/neaMgeepuueZvQ0KICAgIHVzZV9kZWZhdWx0cyA9IEZhbHNlDQogICAgDQogICAgY2VsbHMgPSBfYnVpbGRfY2VsbHNfd2l0aF9sb2FkX3N0YXR1cygNCiAgICAgICAgdGltZV9zbG90cywgDQogICAgICAgIHNlY3Rpb25zLCANCiAgICAgICAgbW90b3Jfc3BlZWQsIA0KICAgICAgICBzdGFydF90aW1lLCANCiAgICAgICAgZW5kX3RpbWUsIA0KICAgICAgICB0ZW1wZXJhdHVyZV9kYXRhLA0KICAgICAgICB1c2VfZGVmYXVsdHM9dXNlX2RlZmF1bHRzDQogICAgKQ0KICAgIA0KICAgICMg5bqU55So6KGM5YGP56e7DQogICAgZm9yIGNlbGwgaW4gY2VsbHM6DQogICAgICAgIGNlbGxbInJvdyJdICs9IDQNCiAgICANCiAgICAjIOa3u+WKoOWunumqjOaXtumXtOS/oeaBr++8iOS4juWOn+Wni+iEmuacrOWujOWFqOS4gOiHtOeahOmAu+i+ke+8iQ0KICAgIHN0YXJ0X3RpbWVfcm93ID0gMQ0KICAgIHN0YXJ0X3RpbWVfdmFsdWVfY29sID0gMQ0KICAgIGVuZF90aW1lX3ZhbHVlX2NvbCA9IDMNCiAgICANCiAgICAjIOiOt+WPluWOn+Wni+aXtumXtOWtl+espuS4sui/m+ihjOWkhOeQhu+8iOS4juWOn+Wni+iEmuacrOS/neaMgeS4gOiHtO+8iQ0KICAgIHN0YXJ0X3N0ciA9IG9zLmVudmlyb24uZ2V0KCJFWFBFUklNRU5UX1NUQVJUIiwgIiIpLnN0cmlwKCkNCiAgICBpZiBzdGFydF9zdHIgYW5kIHN0YXJ0X3RpbWU6DQogICAgICAgIHRyeToNCiAgICAgICAgICAgICMg5L2/55So5LiO5Y6f5aeL6ISa5pys55u45ZCM55qE5pe26Ze05aSE55CG6YC76L6RDQogICAgICAgICAgICB1dGNfYXdhcmVfZHQgPSBkYXRldGltZS5zdHJwdGltZShzdGFydF9zdHIsICIlWS0lbS0lZFQlSDolTTolUyV6IikNCiAgICAgICAgICAgIGxvY2FsX2R0MSA9IHV0Y19hd2FyZV9kdC5hc3RpbWV6b25lKHR6PU5vbmUpDQogICAgICAgICAgICBsb2NhbF9kdDIgPSB1dGNfYXdhcmVfZHQuYXN0aW1lem9uZSh0ej1Ob25lKSArIHRpbWVkZWx0YShob3Vycz0zLjUpDQogICAgICAgICAgICBzdGFydF90aW1lX3ZhbHVlID0gbG9jYWxfZHQxLnN0cmZ0aW1lKCIlWS0lbS0lZCAlSDolTTolUyIpDQogICAgICAgICAgICBlbmRfdGltZV92YWx1ZSA9IGxvY2FsX2R0Mi5zdHJmdGltZSgiJVktJW0tJWQgJUg6JU06JVMiKQ0KICAgICAgICAgICAgY2VsbHMuYXBwZW5kKHsicm93Ijogc3RhcnRfdGltZV9yb3csICJjb2wiOiBzdGFydF90aW1lX3ZhbHVlX2NvbCwgInZhbHVlIjogc3RhcnRfdGltZV92YWx1ZX0pDQogICAgICAgICAgICBjZWxscy5hcHBlbmQoeyJyb3ciOiBzdGFydF90aW1lX3JvdywgImNvbCI6IGVuZF90aW1lX3ZhbHVlX2NvbCwgInZhbHVlIjogZW5kX3RpbWVfdmFsdWV9KQ0KICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6DQogICAgICAgICAgICBMT0dHRVIud2FybmluZygiRmFpbGVkIHRvIHByb2Nlc3MgZXhwZXJpbWVudCB0aW1lIHN0cmluZ3M6ICVzIiwgZSkNCiAgICANCiAgICAjIOafpeivoueOr+Wig+a4qeW6pu+8iOS4juWOn+Wni+iEmuacrOWujOWFqOS4gOiHtOeahOmAu+i+ke+8iQ0KICAgIGluZmx1eF91cmwgPSBvcy5lbnZpcm9uLmdldCgiSU5GTFVYX1VSTCIsICIiKS5zdHJpcCgpDQogICAgaW5mbHV4X29yZyA9IG9zLmVudmlyb24uZ2V0KCJJTkZMVVhfT1JHIiwgIiIpLnN0cmlwKCkNCiAgICBpbmZsdXhfdG9rZW4gPSBvcy5lbnZpcm9uLmdldCgiSU5GTFVYX1RPS0VOIiwgIiIpLnN0cmlwKCkNCiAgICBpbmZsdXhfYnVja2V0ID0gb3MuZW52aXJvbi5nZXQoIklORkxVWF9CVUNLRVQiLCAiUENNIikuc3RyaXAoKQ0KICAgIGluZmx1eF9tZWFzdXJlbWVudCA9IG9zLmVudmlyb24uZ2V0KCJJTkZMVVhfTUVBU1VSRU1FTlQiLCAiUENNX01lYXN1cmVtZW50Iikuc3RyaXAoKQ0KICAgIA0KICAgIGlmIHN0YXJ0X3RpbWUgYW5kIGVuZF90aW1lOg0KICAgICAgICAjIOWvueS6jueOr+Wig+a4qeW6pu+8jOS9v+eUqOaXtumXtOiMg+WbtOafpeivou+8iOS4juWOn+Wni+iEmuacrOmAu+i+keS4gOiHtO+8iQ0KICAgICAgICB2YWx1ZSA9IF9xdWVyeV9pbmZsdXhkYl9yYW5nZV93aXRoX2xvYWRfc3RhdHVzKA0KICAgICAgICAgICAgIueOr+Wig+a4qeW6piIsDQogICAgICAgICAgICBzdGFydF90aW1lLA0KICAgICAgICAgICAgZW5kX3RpbWUsDQogICAgICAgICAgICBpbmZsdXhfdXJsLA0KICAgICAgICAgICAgaW5mbHV4X29yZywNCiAgICAgICAgICAgIGluZmx1eF90b2tlbiwNCiAgICAgICAgICAgIGluZmx1eF9idWNrZXQsDQogICAgICAgICAgICBpbmZsdXhfbWVhc3VyZW1lbnQsDQogICAgICAgICAgICBmaWx0ZXJzPXsiZGF0YV90eXBlIjogIkxTREFRIn0sDQogICAgICAgICkNCiAgICAgICAgIyDnoa7kv512YWx1ZeS4jeaYr05vbmXvvIzpgb/lhY1Xb3JkIENPTeaTjeS9nOW8guW4uO+8iOS4juWOn+Wni+iEmuacrOS4gOiHtO+8iQ0KICAgICAgICBpZiB2YWx1ZSBpcyBub3QgTm9uZToNCiAgICAgICAgICAgIGNlbGxzLmFwcGVuZCh7InJvdyI6IDAsICJjb2wiOiAxLCAidmFsdWUiOiBmInt2YWx1ZTouMWZ9In0pDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICBjZWxscy5hcHBlbmQoeyJyb3ciOiAwLCAiY29sIjogMSwgInZhbHVlIjogIiJ9KQ0KICAgIA0KICAgIExPR0dFUi5pbmZvKA0KICAgICAgICAiVGVtcGVyYXR1cmUgdGFibGUgYnVpbHQgd2l0aCBsb2FkX3N0YXR1cz0xIGZpbHRlcjogdG9rZW49JXMgY2VsbHM9JWQgdGltZV9zbG90cz0lcyIsDQogICAgICAgIHRva2VuLA0KICAgICAgICBsZW4oY2VsbHMpLA0KICAgICAgICAiLCIuam9pbih0aW1lX3Nsb3RzKSwNCiAgICApDQogICAgDQogICAgcmV0dXJuIHsNCiAgICAgICAgInRva2VuIjogdG9rZW4sDQogICAgICAgICJzdGFydFJvdyI6IHJvd19vZmZzZXQsDQogICAgICAgICJzdGFydENvbCI6IGNvbF9vZmZzZXQsDQogICAgICAgICJjZWxscyI6IGNlbGxzLA0KICAgIH0NCg0KDQpkZWYgX2xvYWRfcGF5bG9hZCgpIC0+IERpY3Rbc3RyLCBBbnldOg0KICAgICIiIuS7juagh+WHhui+k+WFpeaIlueOr+Wig+WPmOmHj+WKoOi9vXBheWxvYWTmlbDmja4iIiINCiAgICB0cnk6DQogICAgICAgICMg5bCd6K+V5LuO5qCH5YeG6L6T5YWl6K+75Y+WSlNPTg0KICAgICAgICB0cnk6DQogICAgICAgICAgICBpbXBvcnQgc2VsZWN0DQogICAgICAgICAgICBpZiBzZWxlY3Quc2VsZWN0KFtzeXMuc3RkaW5dLCBbXSwgW10sIDAuMClbMF06DQogICAgICAgICAgICAgICAgcGF5bG9hZF9zdHIgPSBzeXMuc3RkaW4ucmVhZCgpLnN0cmlwKCkNCiAgICAgICAgICAgICAgICBpZiBwYXlsb2FkX3N0cjoNCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGpzb24ubG9hZHMocGF5bG9hZF9zdHIpDQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoNCiAgICAgICAgICAgICMgV2luZG93c+S4inNlbGVjdOWPr+iDveS4jeWPr+eUqO+8jOWwneivleebtOaOpeivu+WPlg0KICAgICAgICAgICAgaW1wb3J0IG1zdmNydA0KICAgICAgICAgICAgaWYgbXN2Y3J0LmtiaGl0KCk6DQogICAgICAgICAgICAgICAgcGF5bG9hZF9zdHIgPSBzeXMuc3RkaW4ucmVhZCgpLnN0cmlwKCkNCiAgICAgICAgICAgICAgICBpZiBwYXlsb2FkX3N0cjoNCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGpzb24ubG9hZHMocGF5bG9hZF9zdHIpDQogICAgZXhjZXB0IEV4Y2VwdGlvbjoNCiAgICAgICAgcGFzcw0KICAgIA0KICAgICMg5aaC5p6c5rKh5pyJ5qCH5YeG6L6T5YWl77yM6L+U5Zue56m65a2X5YW4DQogICAgcmV0dXJuIHt9DQoNCg0KZGVmIF9sb2dfZW52aXJvbm1lbnRfdmFyaWFibGVzKCkgLT4gTm9uZToNCiAgICAiIiLorrDlvZXnm7jlhbPnjq/looPlj5jph48iIiINCiAgICBlbnZfdmFycyA9IFsNCiAgICAgICAgIlRBQkxFX1RPS0VOIiwgIlRBQkxFX1NUQVJUX1JPVyIsICJUQUJMRV9TVEFSVF9DT0wiLCAiVEFCTEVfVElNRV9TTE9UUyIsICJUQUJMRV9NT1RPUl9TUEVFRCIsDQogICAgICAgICJFWFBFUklNRU5UX1NUQVJUIiwgIkVYUEVSSU1FTlRfRU5EIiwNCiAgICAgICAgIklORkxVWF9VUkwiLCAiSU5GTFVYX09SRyIsICJJTkZMVVhfVE9LRU4iLCAiSU5GTFVYX0JVQ0tFVCIsICJJTkZMVVhfTUVBU1VSRU1FTlQiDQogICAgXQ0KICAgIA0KICAgIGZvciB2YXIgaW4gZW52X3ZhcnM6DQogICAgICAgIHZhbHVlID0gb3MuZW52aXJvbi5nZXQodmFyLCAiIikNCiAgICAgICAgaWYgIlRPS0VOIiBpbiB2YXIgYW5kIHZhbHVlOg0KICAgICAgICAgICAgdmFsdWUgPSBfbWFza19zZWNyZXQodmFsdWUpDQogICAgICAgIExPR0dFUi5kZWJ1ZygiRU5WICVzPSVzIiwgdmFyLCB2YWx1ZSBvciAiPGVtcHR5PiIpDQoNCg0KZGVmIG1haW4oKSAtPiBpbnQ6DQogICAgdHJ5Og0KICAgICAgICB0cnk6DQogICAgICAgICAgICBpZiBub3QgbG9nZ2luZy5nZXRMb2dnZXIoKS5oYW5kbGVyczoNCiAgICAgICAgICAgICAgICBsb2dfbGV2ZWxfbmFtZSA9IG9zLmVudmlyb24uZ2V0KCJUQUJMRV9MT0dfTEVWRUwiLCAiREVCVUciKS5zdHJpcCgpIG9yICJERUJVRyINCiAgICAgICAgICAgICAgICBsb2dfbGV2ZWwgPSBnZXRhdHRyKGxvZ2dpbmcsIGxvZ19sZXZlbF9uYW1lLnVwcGVyKCksIGxvZ2dpbmcuREVCVUcpDQogICAgICAgICAgICAgICAgbG9nX2ZpbGVfcmF3ID0gb3MuZW52aXJvbi5nZXQoIlRBQkxFX0xPR19GSUxFIiwgInRlc3QubG9nIikuc3RyaXAoKSBvciAidGVzdC5sb2ciDQogICAgICAgICAgICAgICAgbG9nX2ZpbGUgPSBvcy5wYXRoLmFic3BhdGgobG9nX2ZpbGVfcmF3KQ0KDQogICAgICAgICAgICAgICAgbG9nZ2luZy5iYXNpY0NvbmZpZygNCiAgICAgICAgICAgICAgICAgICAgbGV2ZWw9bG9nX2xldmVsLA0KICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9IiUoYXNjdGltZSlzIFslKGxldmVsbmFtZSlzXSAlKG5hbWUpczogJShtZXNzYWdlKXMiLA0KICAgICAgICAgICAgICAgICAgICBoYW5kbGVycz1bDQogICAgICAgICAgICAgICAgICAgICAgICBsb2dnaW5nLkZpbGVIYW5kbGVyKGxvZ19maWxlLCBlbmNvZGluZz0idXRmLTgiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxvZ2dpbmcuU3RyZWFtSGFuZGxlcihzeXMuc3RkZXJyKSwNCiAgICAgICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgICAgTE9HR0VSLmluZm8oIkxvZ2dpbmcgaW5pdGlhbGl6ZWQgLT4gZmlsZT0lcyBsZXZlbD0lcyIsIGxvZ19maWxlLCBsb2dnaW5nLmdldExldmVsTmFtZShsb2dfbGV2ZWwpKQ0KICAgICAgICAgICAgICAgIF9sb2dfZW52aXJvbm1lbnRfdmFyaWFibGVzKCkNCiAgICAgICAgICAgIHN5cy5zdGRvdXQucmVjb25maWd1cmUoZW5jb2Rpbmc9InV0Zi04IikgICMgdHlwZTogaWdub3JlW2F0dHItZGVmaW5lZF0NCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoNCiAgICAgICAgICAgIHBhc3MNCiAgICAgICAgDQogICAgICAgIHBheWxvYWQgPSBfbG9hZF9wYXlsb2FkKCkNCiAgICAgICAgdGFibGVfc3BlYyA9IGJ1aWxkX3RlbXBlcmF0dXJlX3RhYmxlX3dpdGhfbG9hZF9zdGF0dXMocGF5bG9hZCkNCiAgICAgICAgcmVzdWx0ID0geyJ0YWJsZXMiOiBbdGFibGVfc3BlY119DQogICAgICAgIHByaW50KGpzb24uZHVtcHMocmVzdWx0LCBlbnN1cmVfYXNjaWk9RmFsc2UpKQ0KICAgICAgICByZXR1cm4gMA0KICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjOg0KICAgICAgICBwcmludChmImVycm9yOiB7ZXhjfSIsIGZpbGU9c3lzLnN0ZGVycikNCiAgICAgICAgcmV0dXJuIDENCg0KDQppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOg0KICAgIHN5cy5leGl0KG1haW4oKSkNCg==", + "scriptFile": "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uDQojIC0qLSBjb2Rpbmc6IHV0Zi04IC0qLQ0KIiIiDQrmtYvor5Xpg6jkvY3muKnluqborrDlvZXooajnlJ/miJDohJrmnKzvvIjluKbotJ/ovb3nirbmgIHnrZvpgInvvIkNCg0KLSDlv73nlaXkvKDlhaXnmoQgZXhwZXJpbWVudFByb2Nlc3PvvIzoh6rooYzmnoTpgKDlm7rlrprnu5PmnoTnmoTmlbDmja4NCi0g5LuOIEluZmx1eERCIOafpeivouavj+S4qua1i+ivlemDqOS9jeWcqOWQhOaXtumXtOeCueeahOeerOaXtua4qeW6puWAvA0KLSDmt7vliqAgbG9hZF9zdGF0dXMgPSAxIOeahOetm+mAieadoeS7tu+8jOehruS/neWPquWcqOecn+ato+mHh+mbhuaVsOaNruaXtuiOt+WPlua4qeW6pg0KLSDovpPlh7rmoLzlvI/kuI7lupTnlKjkuK3nmoQgc2NyaXB0VGFibGUg5Y2g5L2N56ym5YW85a65DQotIOm7mOiupOaKiiB7c2NyaXB0VGFibGUxfSDmlL7lnKgi5rWL6K+V6YOo5L2NIuaJgOWcqOeahOWNleWFg+agvA0KDQrnjq/looPlj5jph4/vvJoNCiAgICBUQUJMRV9UT0tFTiAgICAgICAgIOebruagh+WNoOS9jeespu+8jOm7mOiupCBzY3JpcHRUYWJsZTENCiAgICBUQUJMRV9TVEFSVF9ST1cgICAgIOWGmeWFpei1t+Wni+ihjOWBj+enu++8jOm7mOiupCAwDQogICAgVEFCTEVfU1RBUlRfQ09MICAgICDlhpnlhaXotbflp4vliJflgY/np7vvvIzpu5jorqQgMA0KICAgIFRBQkxFX1RJTUVfU0xPVFMgICAg6YCX5Y+35YiG6ZqU55qE5pe26Ze05Yi75bqm77yM6buY6K6kICIwLjVoLDFoLDEuNWgsMmgsMi41aCwzaCwzLjVoIg0KICAgIFRBQkxFX01PVE9SX1NQRUVEICAg55S15py66L2s6YCf5qCH562+77yM6buY6K6kICI5ODBSUE0iDQogICAgRVhQRVJJTUVOVF9TVEFSVCAgICAg5a6e6aqM5byA5aeL5pe26Ze077yISVNPIDg2MDEg5qC85byP77yM5aaCIDIwMjQtMDEtMDFUMTA6MDA6MDBa77yJDQogICAgRVhQRVJJTUVOVF9FTkQgICAgICAg5a6e6aqM57uT5p2f5pe26Ze077yISVNPIDg2MDEg5qC85byP77yJDQogICAgSU5GTFVYX1VSTCAgICAgICAgICAgSW5mbHV4REIgVVJMDQogICAgSU5GTFVYX09SRyAgICAgICAgICAgSW5mbHV4REIg57uE57uHDQogICAgSU5GTFVYX1RPS0VOICAgICAgICAgSW5mbHV4REIg5Luk54mMDQogICAgSU5GTFVYX0JVQ0tFVCAgICAgICAgSW5mbHV4REIg5qG25ZCN77yM6buY6K6kIFBDTQ0KICAgIElORkxVWF9NRUFTVVJFTUVOVCAgIEluZmx1eERCIOa1i+mHj+WQje+8jOm7mOiupCBQQ01fTWVhc3VyZW1lbnQNCiIiIg0KDQpmcm9tIF9fZnV0dXJlX18gaW1wb3J0IGFubm90YXRpb25zDQoNCmltcG9ydCBqc29uDQppbXBvcnQgbG9nZ2luZw0KaW1wb3J0IG9zDQppbXBvcnQgc3lzDQpmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZSwgdGltZWRlbHRhDQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBPcHRpb25hbA0KDQoNCkxPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKF9fbmFtZV9fKQ0KDQoNCmRlZiBfbWFza19zZWNyZXQodmFsdWU6IE9wdGlvbmFsW3N0cl0pIC0+IHN0cjoNCiAgICAiIiLmjqnnoIHmlY/mhJ/kv6Hmga8iIiINCiAgICBpZiBub3QgdmFsdWU6DQogICAgICAgIHJldHVybiAiPGVtcHR5PiINCiAgICBpZiBsZW4odmFsdWUpIDw9IDg6DQogICAgICAgIHJldHVybiAiKiIgKiBsZW4odmFsdWUpDQogICAgcmV0dXJuIHZhbHVlWzo0XSArICIqIiAqIChsZW4odmFsdWUpIC0gOCkgKyB2YWx1ZVstNDpdDQoNCg0KZGVmIF9zZXR1cF9sb2dnaW5nKCkgLT4gTm9uZToNCiAgICAiIiLorr7nva7ml6Xlv5ciIiINCiAgICBsb2dfbGV2ZWxfc3RyID0gb3MuZW52aXJvbi5nZXQoIlRBQkxFX0xPR19MRVZFTCIsICJERUJVRyIpLnVwcGVyKCkNCiAgICBsb2dfbGV2ZWwgPSBnZXRhdHRyKGxvZ2dpbmcsIGxvZ19sZXZlbF9zdHIsIGxvZ2dpbmcuREVCVUcpDQogICAgDQogICAgIyDphY3nva7moLnml6Xlv5forrDlvZXlmagNCiAgICBsb2dnaW5nLmJhc2ljQ29uZmlnKA0KICAgICAgICBsZXZlbD1sb2dfbGV2ZWwsDQogICAgICAgIGZvcm1hdD0nJShhc2N0aW1lKXMgWyUobGV2ZWxuYW1lKXNdICUobmFtZSlzOiAlKG1lc3NhZ2UpcycsDQogICAgICAgIGhhbmRsZXJzPVsNCiAgICAgICAgICAgIGxvZ2dpbmcuU3RyZWFtSGFuZGxlcihzeXMuc3RkZXJyKQ0KICAgICAgICBdDQogICAgKQ0KICAgIA0KICAgICMg5aaC5p6c5oyH5a6a5LqG5pel5b+X5paH5Lu277yM5re75Yqg5paH5Lu25aSE55CG5ZmoDQogICAgbG9nX2ZpbGUgPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfTE9HX0ZJTEUiLCAiIikuc3RyaXAoKQ0KICAgIGlmIGxvZ19maWxlOg0KICAgICAgICB0cnk6DQogICAgICAgICAgICBmaWxlX2hhbmRsZXIgPSBsb2dnaW5nLkZpbGVIYW5kbGVyKGxvZ19maWxlLCBlbmNvZGluZz0ndXRmLTgnKQ0KICAgICAgICAgICAgZmlsZV9oYW5kbGVyLnNldExldmVsKGxvZ19sZXZlbCkNCiAgICAgICAgICAgIGZpbGVfaGFuZGxlci5zZXRGb3JtYXR0ZXIobG9nZ2luZy5Gb3JtYXR0ZXIoDQogICAgICAgICAgICAgICAgJyUoYXNjdGltZSlzIFslKGxldmVsbmFtZSlzXSAlKG5hbWUpczogJShtZXNzYWdlKXMnDQogICAgICAgICAgICApKQ0KICAgICAgICAgICAgbG9nZ2luZy5nZXRMb2dnZXIoKS5hZGRIYW5kbGVyKGZpbGVfaGFuZGxlcikNCiAgICAgICAgICAgIExPR0dFUi5pbmZvKCLml6Xlv5fmlofku7blt7LphY3nva46ICVzIiwgbG9nX2ZpbGUpDQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToNCiAgICAgICAgICAgIExPR0dFUi53YXJuaW5nKCLphY3nva7ml6Xlv5fmlofku7blpLHotKU6ICVzIiwgZSkNCg0KDQpkZWYgX2dldF9pbmZsdXhfY29uZmlnKCkgLT4gRGljdFtzdHIsIHN0cl06DQogICAgIiIi6I635Y+WSW5mbHV4RELphY3nva4iIiINCiAgICBjb25maWcgPSB7DQogICAgICAgICd1cmwnOiBvcy5lbnZpcm9uLmdldCgiSU5GTFVYX1VSTCIsICIiKS5zdHJpcCgpLA0KICAgICAgICAnb3JnJzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9PUkciLCAiIikuc3RyaXAoKSwNCiAgICAgICAgJ3Rva2VuJzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9UT0tFTiIsICIiKS5zdHJpcCgpLA0KICAgICAgICAnYnVja2V0Jzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9CVUNLRVQiLCAiUENNIikuc3RyaXAoKSwNCiAgICAgICAgJ21lYXN1cmVtZW50Jzogb3MuZW52aXJvbi5nZXQoIklORkxVWF9NRUFTVVJFTUVOVCIsICJQQ01fTWVhc3VyZW1lbnQiKS5zdHJpcCgpLA0KICAgIH0NCiAgICANCiAgICBMT0dHRVIuZGVidWcoDQogICAgICAgICJJbmZsdXhEQumFjee9rjogdXJsPSVzIG9yZz0lcyB0b2tlbj0lcyBidWNrZXQ9JXMgbWVhc3VyZW1lbnQ9JXMiLA0KICAgICAgICBjb25maWdbJ3VybCddIG9yICI8ZW1wdHk+IiwNCiAgICAgICAgY29uZmlnWydvcmcnXSBvciAiPGVtcHR5PiIsDQogICAgICAgIF9tYXNrX3NlY3JldChjb25maWdbJ3Rva2VuJ10pLA0KICAgICAgICBjb25maWdbJ2J1Y2tldCddLA0KICAgICAgICBjb25maWdbJ21lYXN1cmVtZW50J10sDQogICAgKQ0KICAgIA0KICAgIHJldHVybiBjb25maWcNCg0KDQpkZWYgX3BhcnNlX2V4cGVyaW1lbnRfdGltZXMoKSAtPiB0dXBsZVtPcHRpb25hbFtkYXRldGltZV0sIE9wdGlvbmFsW2RhdGV0aW1lXV06DQogICAgIiIi6Kej5p6Q5a6e6aqM5pe26Ze0IiIiDQogICAgc3RhcnRfc3RyID0gb3MuZW52aXJvbi5nZXQoIkVYUEVSSU1FTlRfU1RBUlQiLCAiIikuc3RyaXAoKQ0KICAgIGVuZF9zdHIgPSBvcy5lbnZpcm9uLmdldCgiRVhQRVJJTUVOVF9FTkQiLCAiIikuc3RyaXAoKQ0KICAgIA0KICAgIHN0YXJ0X3RpbWU6IE9wdGlvbmFsW2RhdGV0aW1lXSA9IE5vbmUNCiAgICBlbmRfdGltZTogT3B0aW9uYWxbZGF0ZXRpbWVdID0gTm9uZQ0KICAgIA0KICAgIGlmIHN0YXJ0X3N0cjoNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgZm9yIGZtdCBpbiBbIiVZLSVtLSVkVCVIOiVNOiVTWiIsICIlWS0lbS0lZFQlSDolTTolUyV6Il06DQogICAgICAgICAgICAgICAgdHJ5Og0KICAgICAgICAgICAgICAgICAgICBzdGFydF90aW1lID0gZGF0ZXRpbWUuc3RycHRpbWUoc3RhcnRfc3RyLCBmbXQpDQogICAgICAgICAgICAgICAgICAgIGlmIHN0YXJ0X3RpbWUudHppbmZvIGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgICAgICAgICAgICAgIyDovazmjaLkuLrmnKzlnLDml7bpl7TlubbljrvpmaTml7bljLrkv6Hmga8NCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0X3RpbWUgPSBzdGFydF90aW1lLmFzdGltZXpvbmUodHo9Tm9uZSkucmVwbGFjZSh0emluZm89Tm9uZSkNCiAgICAgICAgICAgICAgICAgICAgYnJlYWsNCiAgICAgICAgICAgICAgICBleGNlcHQgVmFsdWVFcnJvcjoNCiAgICAgICAgICAgICAgICAgICAgY29udGludWUNCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOg0KICAgICAgICAgICAgcHJpbnQoZiJXYXJuaW5nOiBGYWlsZWQgdG8gcGFyc2UgRVhQRVJJTUVOVF9TVEFSVCAne3N0YXJ0X3N0cn0nOiB7ZX0iLCBmaWxlPXN5cy5zdGRlcnIpDQogICAgDQogICAgaWYgZW5kX3N0cjoNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgZm9yIGZtdCBpbiBbIiVZLSVtLSVkVCVIOiVNOiVTWiIsICIlWS0lbS0lZFQlSDolTTolUyV6Il06DQogICAgICAgICAgICAgICAgdHJ5Og0KICAgICAgICAgICAgICAgICAgICBlbmRfdGltZSA9IGRhdGV0aW1lLnN0cnB0aW1lKGVuZF9zdHIsIGZtdCkNCiAgICAgICAgICAgICAgICAgICAgaWYgZW5kX3RpbWUudHppbmZvIGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgICAgICAgICAgICAgIyDovazmjaLkuLrmnKzlnLDml7bpl7TlubbljrvpmaTml7bljLrkv6Hmga8NCiAgICAgICAgICAgICAgICAgICAgICAgIGVuZF90aW1lID0gZW5kX3RpbWUuYXN0aW1lem9uZSh0ej1Ob25lKS5yZXBsYWNlKHR6aW5mbz1Ob25lKQ0KICAgICAgICAgICAgICAgICAgICBicmVhaw0KICAgICAgICAgICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOg0KICAgICAgICAgICAgICAgICAgICBjb250aW51ZQ0KICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6DQogICAgICAgICAgICBwcmludChmIldhcm5pbmc6IEZhaWxlZCB0byBwYXJzZSBFWFBFUklNRU5UX0VORCAne2VuZF9zdHJ9Jzoge2V9IiwgZmlsZT1zeXMuc3RkZXJyKQ0KICAgIA0KICAgIHJldHVybiBzdGFydF90aW1lLCBlbmRfdGltZQ0KDQoNCmRlZiBfcGFyc2VfdGltZV9zbG90KHNsb3Rfc3RyOiBzdHIpIC0+IGZsb2F0Og0KICAgICIiIuino+aekOaXtumXtOanveWtl+espuS4suS4uuWwj+aXtuaVsCIiIg0KICAgIGlmIG5vdCBzbG90X3N0cjoNCiAgICAgICAgcmV0dXJuIDAuMA0KICAgIA0KICAgIHNsb3Rfc3RyID0gc2xvdF9zdHIuc3RyaXAoKS5sb3dlcigpDQogICAgDQogICAgaWYgc2xvdF9zdHIuZW5kc3dpdGgoJ2gnKToNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgcmV0dXJuIGZsb2F0KHNsb3Rfc3RyWzotMV0pDQogICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOg0KICAgICAgICAgICAgcGFzcw0KICAgIA0KICAgIHRyeToNCiAgICAgICAgcmV0dXJuIGZsb2F0KHNsb3Rfc3RyKQ0KICAgIGV4Y2VwdCBWYWx1ZUVycm9yOg0KICAgICAgICBwYXNzDQogICAgDQogICAgcmV0dXJuIDAuMA0KDQoNCmRlZiBfdGltZV9zbG90cygpIC0+IExpc3Rbc3RyXToNCiAgICByYXcgPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfVElNRV9TTE9UUyIsICIiKS5zdHJpcCgpDQogICAgaWYgbm90IHJhdzoNCiAgICAgICAgIyDmoLnmja7lm77niYfvvIzml7bpl7TliLvluqbmmK/vvJowLjVoLCAxaCwgMS41aCwgMmgsIDIuNWgsIDNoLCAzLjVo77yIN+WIl++8iQ0KICAgICAgICByZXR1cm4gWyIwLjVoIiwgIjFoIiwgIjEuNWgiLCAiMmgiLCAiMi41aCIsICIzaCIsICIzLjVoIl0NCiAgICBzbG90cyA9IFtzbG90LnN0cmlwKCkgZm9yIHNsb3QgaW4gcmF3LnNwbGl0KCIsIildDQogICAgcmV0dXJuIFtzbG90IGZvciBzbG90IGluIHNsb3RzIGlmIHNsb3RdDQoNCg0KZGVmIF9kZWZhdWx0X3NlY3Rpb25zKCkgLT4gTGlzdFtEaWN0W3N0ciwgQW55XV06DQogICAgIyBuYW1lIC0+IHJvd3MgdW5kZXJuZWF0aO+8iGVudHJpZXPvvIkNCiAgICAjIOavj+S4qiBlbnRyeSDlr7nlupTkuIDkuKrmtYvor5Xpg6jkvY3vvIzpnIDopoHmmKDlsITliLAgSW5mbHV4REIg55qEIGZpZWxkIOaIliB0YWcNCiAgICByZXR1cm4gWw0KICAgICAgICB7Im5hbWUiOiAi5Li76L205om/IiwgImVudHJpZXMiOiBbDQogICAgICAgICAgICB7ImxhYmVsIjogIiMxIiwgImZpZWxkIjogIuS4u+i9tOaJvyMxIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLkuLvovbTmib8jMSJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjMiIsICJmaWVsZCI6ICLkuLvovbTmib8jMiIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5Li76L205om/IzIifSwNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzMiLCAiZmllbGQiOiAi5Li76L205om/IzMiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuS4u+i9tOaJvyMzIn0sDQogICAgICAgICAgICB7ImxhYmVsIjogIiM0IiwgImZpZWxkIjogIuS4u+i9tOaJvyM0IiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLkuLvovbTmib8jNCJ9LA0KICAgICAgICBdfSwNCiAgICAgICAgeyJuYW1lIjogIuWNgeWtl+WktCIsICJlbnRyaWVzIjogWw0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjMSIsICJmaWVsZCI6ICLljYHlrZflpLQjMSIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5Y2B5a2X5aS0IzEifSwNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzIiLCAiZmllbGQiOiAi5Y2B5a2X5aS0IzIiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuWNgeWtl+WktCMyIn0sDQogICAgICAgICAgICB7ImxhYmVsIjogIiMzIiwgImZpZWxkIjogIuWNgeWtl+WktCMzIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLljYHlrZflpLQjMyJ9LA0KICAgICAgICBdfSwNCiAgICAgICAgeyJuYW1lIjogIuWHj+mAn+euseWwj+i9tOaJvyIsICJlbnRyaWVzIjogWw0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjMe+8iOi+k+WFpeazleWFsOerr++8iSIsICJmaWVsZCI6ICLlh4/pgJ/nrrHlsI/ovbTmib8xIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLlh4/pgJ/nrrHlsI/ovbTmib8jMSJ9LA0KICAgICAgICAgICAgeyJsYWJlbCI6ICIjMiIsICJmaWVsZCI6ICLlh4/pgJ/nrrHlsI/ovbTmib8jMiIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAiTFNEQVEifSwgInJlc3VsdF9rZXkiOiAi5YeP6YCf566x5bCP6L205om/IzIifSwNCiAgICAgICAgXX0sDQogICAgICAgIHsibmFtZSI6ICLlh4/pgJ/nrrHlpKfovbTmib8iLCAiZW50cmllcyI6IFsNCiAgICAgICAgICAgIHsibGFiZWwiOiAiIzPvvIjlpKfnq6/nm5bnq6/vvIkiLCAiZmllbGQiOiAi5YeP6YCf566x5aSn6L205om/IzMiLCAiZmlsdGVycyI6IHsiZGF0YV90eXBlIjogIkxTREFRIn0sICJyZXN1bHRfa2V5IjogIuWHj+mAn+euseWkp+i9tOaJvyMzIn0sDQogICAgICAgICAgICB7ImxhYmVsIjogIiM0IiwgImZpZWxkIjogIuWHj+mAn+euseWkp+i9tOaJvyM0IiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICJMU0RBUSJ9LCAicmVzdWx0X2tleSI6ICLlh4/pgJ/nrrHlpKfovbTmib8jNCJ9LA0KICAgICAgICBdfSwNCiAgICAgICAgeyJuYW1lIjogIua2pua7keayuea4qSIsICJlbnRyaWVzIjogWw0KICAgICAgICAgICAgeyJsYWJlbCI6ICIiLCAiZmllbGQiOiAibWVhbiIsICJmaWx0ZXJzIjogeyJkYXRhX3R5cGUiOiAi5ram5ruR5rK55ripIn0sICJyZXN1bHRfa2V5IjogIua2pua7keayuea4qSJ9LA0KICAgICAgICBdfSwNCiAgICAgICAgeyJuYW1lIjogIua2pua7keayueWOiyIsICJlbnRyaWVzIjogWw0KICAgICAgICAgICAgeyJsYWJlbCI6ICIoUHNpKSIsICJmaWVsZCI6ICJtZWFuIiwgImZpbHRlcnMiOiB7ImRhdGFfdHlwZSI6ICLmtqbmu5HmsrnljosifSwgInJlc3VsdF9rZXkiOiAi5ram5ruR5rK55Y6LIn0sDQogICAgICAgIF19LA0KICAgIF0NCg0KZGVmIF9xdWVyeV9sb2FkX3N0YXR1c190aW1lbGluZSgNCiAgICBzdGFydF90aW1lOiBkYXRldGltZSwNCiAgICBlbmRfdGltZTogZGF0ZXRpbWUsDQogICAgaW5mbHV4X3VybDogc3RyLA0KICAgIGluZmx1eF9vcmc6IHN0ciwNCiAgICBpbmZsdXhfdG9rZW46IHN0ciwNCiAgICBpbmZsdXhfYnVja2V0OiBzdHIsDQogICAgaW5mbHV4X21lYXN1cmVtZW50OiBzdHIsDQopIC0+IExpc3RbRGljdFtzdHIsIEFueV1dOg0KICAgICIiIuafpeivouaVtOS4quWunumqjOacn+mXtOeahGxvYWRfc3RhdHVz5pe26Ze057q/5pWw5o2uIiIiDQogICAgdHJ5Og0KICAgICAgICBmcm9tIGluZmx1eGRiX2NsaWVudCBpbXBvcnQgSW5mbHV4REJDbGllbnQNCiAgICAgICAgaW1wb3J0IHBhbmRhcyBhcyBwZA0KICAgICAgICBpbXBvcnQgd2FybmluZ3MNCiAgICAgICAgZnJvbSBpbmZsdXhkYl9jbGllbnQuY2xpZW50Lndhcm5pbmdzIGltcG9ydCBNaXNzaW5nUGl2b3RGdW5jdGlvbg0KICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoNCiAgICAgICAgTE9HR0VSLndhcm5pbmcoIkluZmx1eERCIGNsaWVudCBub3QgYXZhaWxhYmxlLCBza2lwIGxvYWRfc3RhdHVzIHRpbWVsaW5lIHF1ZXJ5IikNCiAgICAgICAgcmV0dXJuIFtdDQoNCiAgICB0cnk6DQogICAgICAgIGNsaWVudCA9IEluZmx1eERCQ2xpZW50KHVybD1pbmZsdXhfdXJsLCBvcmc9aW5mbHV4X29yZywgdG9rZW49aW5mbHV4X3Rva2VuKQ0KICAgICAgICBxdWVyeV9hcGkgPSBjbGllbnQucXVlcnlfYXBpKCkNCg0KICAgICAgICBzdGFydF9yZmMgPSBzdGFydF90aW1lLnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKQ0KICAgICAgICBlbmRfcmZjID0gZW5kX3RpbWUuc3RyZnRpbWUoJyVZLSVtLSVkVCVIOiVNOiVTWicpDQoNCiAgICAgICAgIyDmn6Xor6Jsb2FkX3N0YXR1c+Wtl+auteeahOaJgOacieaVsOaNrueCue+8iOWcqEJyZWFrZXLmlbDmja7nsbvlnovkuK3vvIkNCiAgICAgICAgZmx1eCA9IGYnJycNCmZyb20oYnVja2V0OiAie2luZmx1eF9idWNrZXR9IikNCiAgfD4gcmFuZ2Uoc3RhcnQ6IHtzdGFydF9yZmN9LCBzdG9wOiB7ZW5kX3JmY30pDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl9tZWFzdXJlbWVudCJdID09ICJ7aW5mbHV4X21lYXN1cmVtZW50fSIpDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbImRhdGFfdHlwZSJdID09ICJCcmVha2VyIikNCiAgfD4gZmlsdGVyKGZuOiAocikgPT4gclsiX2ZpZWxkIl0gPT0gImxvYWRfc3RhdHVzIikNCiAgfD4gc29ydChjb2x1bW5zOiBbIl90aW1lIl0pDQogIHw+IHlpZWxkKG5hbWU6ICJsb2FkX3N0YXR1c190aW1lbGluZSIpDQonJycuc3RyaXAoKQ0KDQogICAgICAgIExPR0dFUi5kZWJ1ZygiTG9hZCBzdGF0dXMgdGltZWxpbmUgcXVlcnk6XG4lcyIsIGZsdXgpDQoNCiAgICAgICAgd2l0aCB3YXJuaW5ncy5jYXRjaF93YXJuaW5ncygpOg0KICAgICAgICAgICAgd2FybmluZ3Muc2ltcGxlZmlsdGVyKCJpZ25vcmUiLCBNaXNzaW5nUGl2b3RGdW5jdGlvbikNCiAgICAgICAgICAgIGZyYW1lcyA9IHF1ZXJ5X2FwaS5xdWVyeV9kYXRhX2ZyYW1lKGZsdXgpDQogICAgICAgIA0KICAgICAgICBpZiBpc2luc3RhbmNlKGZyYW1lcywgbGlzdCk6DQogICAgICAgICAgICBkZiA9IHBkLmNvbmNhdChmcmFtZXMsIGlnbm9yZV9pbmRleD1UcnVlKSBpZiBmcmFtZXMgZWxzZSBwZC5EYXRhRnJhbWUoKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgZGYgPSBmcmFtZXMNCg0KICAgICAgICBpZiBkZi5lbXB0eSBvciAnX3ZhbHVlJyBub3QgaW4gZGYuY29sdW1ucyBvciAnX3RpbWUnIG5vdCBpbiBkZi5jb2x1bW5zOg0KICAgICAgICAgICAgTE9HR0VSLndhcm5pbmcoIk5vIGxvYWRfc3RhdHVzIHRpbWVsaW5lIGRhdGEgZm91bmQiKQ0KICAgICAgICAgICAgcmV0dXJuIFtdDQoNCiAgICAgICAgIyDovazmjaLkuLrml7bpl7Tnur/mlbDmja7vvIznoa7kv53ml7bljLrkuIDoh7TmgKcNCiAgICAgICAgdGltZWxpbmUgPSBbXQ0KICAgICAgICBmb3IgXywgcm93IGluIGRmLml0ZXJyb3dzKCk6DQogICAgICAgICAgICB0aW1lX29iaiA9IHBkLnRvX2RhdGV0aW1lKHJvd1snX3RpbWUnXSkNCiAgICAgICAgICAgICMg6L2s5o2i5Li65pys5Zyw5pe26Ze077yM5Y676Zmk5pe25Yy65L+h5oGv77yM5LiOc3RhcnRfdGltZS9lbmRfdGltZeS/neaMgeS4gOiHtA0KICAgICAgICAgICAgaWYgaGFzYXR0cih0aW1lX29iaiwgJ3R6JykgYW5kIHRpbWVfb2JqLnR6IGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgICAgICMg5a+55LqOcGFuZGFzIFRpbWVzdGFtcO+8jOWFiOi9rOaNouS4uuacrOWcsOaXtuWMuuWGjei9rOS4ulB5dGhvbiBkYXRldGltZQ0KICAgICAgICAgICAgICAgIHRpbWVfb2JqID0gdGltZV9vYmoudHpfY29udmVydChOb25lKS50b19weWRhdGV0aW1lKCkNCiAgICAgICAgICAgIGVsaWYgaGFzYXR0cih0aW1lX29iaiwgJ3RvX3B5ZGF0ZXRpbWUnKToNCiAgICAgICAgICAgICAgICAjIOi9rOaNouS4ulB5dGhvbiBkYXRldGltZeWvueixoQ0KICAgICAgICAgICAgICAgIHRpbWVfb2JqID0gdGltZV9vYmoudG9fcHlkYXRldGltZSgpDQogICAgICAgICAgICANCiAgICAgICAgICAgICMg56Gu5L+d5rKh5pyJ5pe25Yy65L+h5oGvDQogICAgICAgICAgICBpZiBoYXNhdHRyKHRpbWVfb2JqLCAndHppbmZvJykgYW5kIHRpbWVfb2JqLnR6aW5mbyBpcyBub3QgTm9uZToNCiAgICAgICAgICAgICAgICB0aW1lX29iaiA9IHRpbWVfb2JqLnJlcGxhY2UodHppbmZvPU5vbmUpDQogICAgICAgICAgICAgICAgDQogICAgICAgICAgICB0aW1lbGluZS5hcHBlbmQoew0KICAgICAgICAgICAgICAgICd0aW1lJzogdGltZV9vYmosDQogICAgICAgICAgICAgICAgJ2xvYWRfc3RhdHVzJzogZmxvYXQocm93WydfdmFsdWUnXSkNCiAgICAgICAgICAgIH0pDQoNCiAgICAgICAgTE9HR0VSLmluZm8oIkxvYWQgc3RhdHVzIHRpbWVsaW5lOiAlZCBkYXRhIHBvaW50cyBmcm9tICVzIHRvICVzIiwgDQogICAgICAgICAgICAgICAgICAgbGVuKHRpbWVsaW5lKSwgc3RhcnRfdGltZSwgZW5kX3RpbWUpDQogICAgICAgIA0KICAgICAgICAjIOiwg+ivle+8muajgOafpeaXtumXtOWvueixoeexu+Weiw0KICAgICAgICBpZiB0aW1lbGluZToNCiAgICAgICAgICAgIGZpcnN0X3RpbWUgPSB0aW1lbGluZVswXVsndGltZSddDQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIlRpbWVsaW5lIGZpcnN0IHRpbWU6ICVzICh0eXBlOiAlcywgdHppbmZvOiAlcykiLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGZpcnN0X3RpbWUsIHR5cGUoZmlyc3RfdGltZSksIGdldGF0dHIoZmlyc3RfdGltZSwgJ3R6aW5mbycsIE5vbmUpKQ0KICAgICAgICBMT0dHRVIuZGVidWcoInN0YXJ0X3RpbWU6ICVzICh0eXBlOiAlcywgdHppbmZvOiAlcykiLCANCiAgICAgICAgICAgICAgICAgICAgc3RhcnRfdGltZSwgdHlwZShzdGFydF90aW1lKSwgZ2V0YXR0cihzdGFydF90aW1lLCAndHppbmZvJywgTm9uZSkpDQogICAgICAgIExPR0dFUi5kZWJ1ZygiZW5kX3RpbWU6ICVzICh0eXBlOiAlcywgdHppbmZvOiAlcykiLCANCiAgICAgICAgICAgICAgICAgICAgZW5kX3RpbWUsIHR5cGUoZW5kX3RpbWUpLCBnZXRhdHRyKGVuZF90aW1lLCAndHppbmZvJywgTm9uZSkpDQogICAgICAgIA0KICAgICAgICByZXR1cm4gdGltZWxpbmUNCg0KICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToNCiAgICAgICAgTE9HR0VSLmVycm9yKCJFcnJvciBxdWVyeWluZyBsb2FkX3N0YXR1cyB0aW1lbGluZTogJXMiLCBlKQ0KICAgICAgICByZXR1cm4gW10NCiAgICBmaW5hbGx5Og0KICAgICAgICB0cnk6DQogICAgICAgICAgICBjbGllbnQuY2xvc2UoKQ0KICAgICAgICBleGNlcHQgRXhjZXB0aW9uOg0KICAgICAgICAgICAgcGFzcw0KDQoNCmRlZiBfY2FsY3VsYXRlX2VmZmVjdGl2ZV90aW1lX3BvaW50cygNCiAgICBzdGFydF90aW1lOiBkYXRldGltZSwNCiAgICBlbmRfdGltZTogZGF0ZXRpbWUsDQogICAgdGltZV9zbG90czogTGlzdFtzdHJdLA0KICAgIGluZmx1eF9jb25maWc6IERpY3Rbc3RyLCBzdHJdDQopIC0+IERpY3Rbc3RyLCBPcHRpb25hbFtkYXRldGltZV1dOg0KICAgICIiIuiuoeeul+WfuuS6juacieaViOi/kOihjOaXtumXtOe0r+iuoeeahOecn+WunuaXtumXtOeCuSIiIg0KICAgIA0KICAgICMgMS4g6I635Y+WbG9hZF9zdGF0dXPml7bpl7Tnur8NCiAgICB0aW1lbGluZSA9IF9xdWVyeV9sb2FkX3N0YXR1c190aW1lbGluZSgNCiAgICAgICAgc3RhcnRfdGltZSwgZW5kX3RpbWUsDQogICAgICAgIGluZmx1eF9jb25maWdbJ3VybCddLCBpbmZsdXhfY29uZmlnWydvcmcnXSwgaW5mbHV4X2NvbmZpZ1sndG9rZW4nXSwNCiAgICAgICAgaW5mbHV4X2NvbmZpZ1snYnVja2V0J10sIGluZmx1eF9jb25maWdbJ21lYXN1cmVtZW50J10NCiAgICApDQogICAgDQogICAgaWYgbm90IHRpbWVsaW5lOg0KICAgICAgICBMT0dHRVIud2FybmluZygiTm8gbG9hZF9zdGF0dXMgdGltZWxpbmUgZGF0YSwgZmFsbGJhY2sgdG8gb3JpZ2luYWwgdGltZSBjYWxjdWxhdGlvbiIpDQogICAgICAgICMg5Zue6YCA5Yiw5Y6f5aeL5pe26Ze06K6h566XDQogICAgICAgIHJlc3VsdCA9IHt9DQogICAgICAgIGZvciBzbG90X3N0ciBpbiB0aW1lX3Nsb3RzOg0KICAgICAgICAgICAgc2xvdF9ob3VycyA9IF9wYXJzZV90aW1lX3Nsb3Qoc2xvdF9zdHIpDQogICAgICAgICAgICByZXN1bHRbc2xvdF9zdHJdID0gc3RhcnRfdGltZSArIHRpbWVkZWx0YShob3Vycz1zbG90X2hvdXJzKQ0KICAgICAgICByZXR1cm4gcmVzdWx0DQogICAgDQogICAgIyAyLiDorqHnrpfmnInmlYjov5DooYzml7bpl7TmrrUNCiAgICBlZmZlY3RpdmVfcGVyaW9kcyA9IFtdDQogICAgY3VycmVudF9wZXJpb2Rfc3RhcnQgPSBOb25lDQogICAgDQogICAgZm9yIGksIHBvaW50IGluIGVudW1lcmF0ZSh0aW1lbGluZSk6DQogICAgICAgIGlmIHBvaW50Wydsb2FkX3N0YXR1cyddID09IDEuMDoNCiAgICAgICAgICAgIGlmIGN1cnJlbnRfcGVyaW9kX3N0YXJ0IGlzIE5vbmU6DQogICAgICAgICAgICAgICAgY3VycmVudF9wZXJpb2Rfc3RhcnQgPSBwb2ludFsndGltZSddDQogICAgICAgIGVsc2U6ICAjIGxvYWRfc3RhdHVzICE9IDEuMA0KICAgICAgICAgICAgaWYgY3VycmVudF9wZXJpb2Rfc3RhcnQgaXMgbm90IE5vbmU6DQogICAgICAgICAgICAgICAgZWZmZWN0aXZlX3BlcmlvZHMuYXBwZW5kKHsNCiAgICAgICAgICAgICAgICAgICAgJ3N0YXJ0JzogY3VycmVudF9wZXJpb2Rfc3RhcnQsDQogICAgICAgICAgICAgICAgICAgICdlbmQnOiBwb2ludFsndGltZSddLA0KICAgICAgICAgICAgICAgICAgICAnZHVyYXRpb25faG91cnMnOiAocG9pbnRbJ3RpbWUnXSAtIGN1cnJlbnRfcGVyaW9kX3N0YXJ0KS50b3RhbF9zZWNvbmRzKCkgLyAzNjAwLjANCiAgICAgICAgICAgICAgICB9KQ0KICAgICAgICAgICAgICAgIGN1cnJlbnRfcGVyaW9kX3N0YXJ0ID0gTm9uZQ0KICAgIA0KICAgICMg5aSE55CG5pyA5ZCO5LiA5Liq5ZGo5pyf77yI5aaC5p6c5a6e6aqM57uT5p2f5pe25LuN5Zyo6L+Q6KGM77yJDQogICAgaWYgY3VycmVudF9wZXJpb2Rfc3RhcnQgaXMgbm90IE5vbmU6DQogICAgICAgIGVmZmVjdGl2ZV9wZXJpb2RzLmFwcGVuZCh7DQogICAgICAgICAgICAnc3RhcnQnOiBjdXJyZW50X3BlcmlvZF9zdGFydCwNCiAgICAgICAgICAgICdlbmQnOiBlbmRfdGltZSwNCiAgICAgICAgICAgICdkdXJhdGlvbl9ob3Vycyc6IChlbmRfdGltZSAtIGN1cnJlbnRfcGVyaW9kX3N0YXJ0KS50b3RhbF9zZWNvbmRzKCkgLyAzNjAwLjANCiAgICAgICAgfSkNCiAgICANCiAgICB0b3RhbF9lZmZlY3RpdmVfaG91cnMgPSBzdW0ocGVyaW9kWydkdXJhdGlvbl9ob3VycyddIGZvciBwZXJpb2QgaW4gZWZmZWN0aXZlX3BlcmlvZHMpDQogICAgTE9HR0VSLmluZm8oIkVmZmVjdGl2ZSBydW5uaW5nIHBlcmlvZHM6ICVkIHBlcmlvZHMsIHRvdGFsICUuM2YgaG91cnMiLCANCiAgICAgICAgICAgICAgIGxlbihlZmZlY3RpdmVfcGVyaW9kcyksIHRvdGFsX2VmZmVjdGl2ZV9ob3VycykNCiAgICANCiAgICBmb3IgcGVyaW9kIGluIGVmZmVjdGl2ZV9wZXJpb2RzOg0KICAgICAgICBMT0dHRVIuZGVidWcoIkVmZmVjdGl2ZSBwZXJpb2Q6ICVzIOKGkiAlcyAoJS4zZiBob3VycykiLA0KICAgICAgICAgICAgICAgICAgICBwZXJpb2RbJ3N0YXJ0J10uc3RyZnRpbWUoJyVIOiVNOiVTJyksDQogICAgICAgICAgICAgICAgICAgIHBlcmlvZFsnZW5kJ10uc3RyZnRpbWUoJyVIOiVNOiVTJyksDQogICAgICAgICAgICAgICAgICAgIHBlcmlvZFsnZHVyYXRpb25faG91cnMnXSkNCiAgICANCiAgICAjIDMuIOiuoeeul+avj+S4quaXtumXtOanveWvueW6lOeahOecn+WunuaXtumXtOeCuQ0KICAgIGVmZmVjdGl2ZV90aW1lX3BvaW50cyA9IHt9DQogICAgDQogICAgZm9yIHNsb3Rfc3RyIGluIHRpbWVfc2xvdHM6DQogICAgICAgIHRhcmdldF9lZmZlY3RpdmVfaG91cnMgPSBfcGFyc2VfdGltZV9zbG90KHNsb3Rfc3RyKQ0KICAgICAgICANCiAgICAgICAgaWYgdGFyZ2V0X2VmZmVjdGl2ZV9ob3VycyA8PSAwOg0KICAgICAgICAgICAgZWZmZWN0aXZlX3RpbWVfcG9pbnRzW3Nsb3Rfc3RyXSA9IE5vbmUNCiAgICAgICAgICAgIGNvbnRpbnVlDQogICAgICAgIA0KICAgICAgICBpZiB0YXJnZXRfZWZmZWN0aXZlX2hvdXJzID4gdG90YWxfZWZmZWN0aXZlX2hvdXJzOg0KICAgICAgICAgICAgTE9HR0VSLndhcm5pbmcoIlRhcmdldCBlZmZlY3RpdmUgdGltZSAlLjNmaCBleGNlZWRzIHRvdGFsIGVmZmVjdGl2ZSB0aW1lICUuM2ZoIGZvciBzbG90ICVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X2VmZmVjdGl2ZV9ob3VycywgdG90YWxfZWZmZWN0aXZlX2hvdXJzLCBzbG90X3N0cikNCiAgICAgICAgICAgIGVmZmVjdGl2ZV90aW1lX3BvaW50c1tzbG90X3N0cl0gPSBOb25lDQogICAgICAgICAgICBjb250aW51ZQ0KICAgICAgICANCiAgICAgICAgIyDlnKjmnInmlYjml7bpl7TmrrXkuK3mn6Xmib7ntK/orqHov5DooYx0YXJnZXRfZWZmZWN0aXZlX2hvdXJz5bCP5pe255qE5pe26Ze054K5DQogICAgICAgIGN1bXVsYXRpdmVfaG91cnMgPSAwLjANCiAgICAgICAgdGFyZ2V0X3RpbWVfcG9pbnQgPSBOb25lDQogICAgICAgIA0KICAgICAgICBmb3IgcGVyaW9kIGluIGVmZmVjdGl2ZV9wZXJpb2RzOg0KICAgICAgICAgICAgcGVyaW9kX2R1cmF0aW9uID0gcGVyaW9kWydkdXJhdGlvbl9ob3VycyddDQogICAgICAgICAgICANCiAgICAgICAgICAgIGlmIGN1bXVsYXRpdmVfaG91cnMgKyBwZXJpb2RfZHVyYXRpb24gPj0gdGFyZ2V0X2VmZmVjdGl2ZV9ob3VyczoNCiAgICAgICAgICAgICAgICAjIOebruagh+aXtumXtOeCueWcqOi/meS4quWRqOacn+WGhQ0KICAgICAgICAgICAgICAgIHJlbWFpbmluZ19ob3VycyA9IHRhcmdldF9lZmZlY3RpdmVfaG91cnMgLSBjdW11bGF0aXZlX2hvdXJzDQogICAgICAgICAgICAgICAgdGFyZ2V0X3RpbWVfcG9pbnQgPSBwZXJpb2RbJ3N0YXJ0J10gKyB0aW1lZGVsdGEoaG91cnM9cmVtYWluaW5nX2hvdXJzKQ0KICAgICAgICAgICAgICAgIGJyZWFrDQogICAgICAgICAgICBlbHNlOg0KICAgICAgICAgICAgICAgIGN1bXVsYXRpdmVfaG91cnMgKz0gcGVyaW9kX2R1cmF0aW9uDQogICAgICAgIA0KICAgICAgICBlZmZlY3RpdmVfdGltZV9wb2ludHNbc2xvdF9zdHJdID0gdGFyZ2V0X3RpbWVfcG9pbnQNCiAgICAgICAgDQogICAgICAgIGlmIHRhcmdldF90aW1lX3BvaW50Og0KICAgICAgICAgICAgTE9HR0VSLmluZm8oIlNsb3QgJXM6IGVmZmVjdGl2ZSAlLjNmaCDihpIgYWN0dWFsIHRpbWUgJXMiLA0KICAgICAgICAgICAgICAgICAgICAgICBzbG90X3N0ciwgdGFyZ2V0X2VmZmVjdGl2ZV9ob3VycywgdGFyZ2V0X3RpbWVfcG9pbnQuc3RyZnRpbWUoJyVIOiVNOiVTJykpDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICBMT0dHRVIud2FybmluZygiQ291bGQgbm90IGNhbGN1bGF0ZSBlZmZlY3RpdmUgdGltZSBwb2ludCBmb3Igc2xvdCAlcyIsIHNsb3Rfc3RyKQ0KICAgIA0KICAgIHJldHVybiBlZmZlY3RpdmVfdGltZV9wb2ludHMNCg0KDQpkZWYgX3F1ZXJ5X2luZmx1eGRiX3JhbmdlX3dpdGhfbG9hZF9zdGF0dXMoDQogICAgZmllbGRfbmFtZTogc3RyLA0KICAgIHN0YXJ0X3RpbWU6IGRhdGV0aW1lLA0KICAgIGVuZF90aW1lOiBkYXRldGltZSwNCiAgICBpbmZsdXhfdXJsOiBzdHIsDQogICAgaW5mbHV4X29yZzogc3RyLA0KICAgIGluZmx1eF90b2tlbjogc3RyLA0KICAgIGluZmx1eF9idWNrZXQ6IHN0ciwNCiAgICBpbmZsdXhfbWVhc3VyZW1lbnQ6IHN0ciwNCiAgICBmaWx0ZXJzOiBPcHRpb25hbFtEaWN0W3N0ciwgc3RyXV0gPSBOb25lLA0KKSAtPiBPcHRpb25hbFtmbG9hdF06DQogICAgIiIi5p+l6K+iIEluZmx1eERCIOiOt+WPluaMh+WumuWtl+auteWcqOaXtumXtOiMg+WbtOWGheeahOW5s+Wdh+WAvO+8iOS7heW9kyBsb2FkX3N0YXR1cyA9IDEg5pe277yJIiIiDQogICAgdHJ5Og0KICAgICAgICBmcm9tIGluZmx1eGRiX2NsaWVudCBpbXBvcnQgSW5mbHV4REJDbGllbnQNCiAgICAgICAgaW1wb3J0IHBhbmRhcyBhcyBwZA0KICAgICAgICBpbXBvcnQgd2FybmluZ3MNCiAgICAgICAgZnJvbSBpbmZsdXhkYl9jbGllbnQuY2xpZW50Lndhcm5pbmdzIGltcG9ydCBNaXNzaW5nUGl2b3RGdW5jdGlvbg0KICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoNCiAgICAgICAgTE9HR0VSLndhcm5pbmcoIkluZmx1eERCIGNsaWVudCBub3QgYXZhaWxhYmxlLCBza2lwIHF1ZXJ5IGZvciBmaWVsZD0lcyIsIGZpZWxkX25hbWUpDQogICAgICAgIHJldHVybiBOb25lDQoNCiAgICB0cnk6DQogICAgICAgIGNsaWVudCA9IEluZmx1eERCQ2xpZW50KHVybD1pbmZsdXhfdXJsLCBvcmc9aW5mbHV4X29yZywgdG9rZW49aW5mbHV4X3Rva2VuKQ0KICAgICAgICBxdWVyeV9hcGkgPSBjbGllbnQucXVlcnlfYXBpKCkNCg0KICAgICAgICBzdGFydF9yZmMgPSBzdGFydF90aW1lLnN0cmZ0aW1lKCclWS0lbS0lZFQlSDolTTolU1onKQ0KICAgICAgICBlbmRfcmZjID0gZW5kX3RpbWUuc3RyZnRpbWUoJyVZLSVtLSVkVCVIOiVNOiVTWicpDQoNCiAgICAgICAgIyDmnoTlu7rov4fmu6TmnaHku7YNCiAgICAgICAgdGFnX2ZpbHRlcnMgPSAiIg0KICAgICAgICBpZiBmaWx0ZXJzOg0KICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgaW4gZmlsdGVycy5pdGVtcygpOg0KICAgICAgICAgICAgICAgIHRhZ19maWx0ZXJzICs9IGYnXG4gIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIntrZXl9Il0gPT0gInt2YWx1ZX0iKScNCg0KICAgICAgICAjIOWvueS6jueOr+Wig+a4qeW6pu+8jOWPluWFqOmDqOmdnjDmlbDmja7nmoTlnYflgLzvvJvlhbbku5blrZfmrrXku43pnIBsb2FkX3N0YXR1cz0x562b6YCJDQogICAgICAgIGlmIGZpZWxkX25hbWUgPT0gIueOr+Wig+a4qeW6piI6DQogICAgICAgICAgICBmbHV4ID0gZicnJw0KZnJvbShidWNrZXQ6ICJ7aW5mbHV4X2J1Y2tldH0iKQ0KICB8PiByYW5nZShzdGFydDoge3N0YXJ0X3JmY30sIHN0b3A6IHtlbmRfcmZjfSkNCiAgfD4gZmlsdGVyKGZuOiAocikgPT4gclsiX21lYXN1cmVtZW50Il0gPT0gIntpbmZsdXhfbWVhc3VyZW1lbnR9IikNCiAgfD4gZmlsdGVyKGZuOiAocikgPT4gclsiX2ZpZWxkIl0gPT0gIntmaWVsZF9uYW1lfSIpDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl92YWx1ZSJdICE9IDAuMCl7dGFnX2ZpbHRlcnN9DQogIHw+IG1lYW4oKQ0KICB8PiB5aWVsZChuYW1lOiAibWVhbl9ub25femVybyIpDQonJycuc3RyaXAoKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgZmx1eCA9IGYnJycNCmZyb20oYnVja2V0OiAie2luZmx1eF9idWNrZXR9IikNCiAgfD4gcmFuZ2Uoc3RhcnQ6IHtzdGFydF9yZmN9LCBzdG9wOiB7ZW5kX3JmY30pDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl9tZWFzdXJlbWVudCJdID09ICJ7aW5mbHV4X21lYXN1cmVtZW50fSIpDQogIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIl9maWVsZCJdID09ICJ7ZmllbGRfbmFtZX0iKXt0YWdfZmlsdGVyc30NCiAgfD4gbWVhbigpDQogIHw+IHlpZWxkKG5hbWU6ICJtZWFuX3RlbXBlcmF0dXJlX2RhdGEiKQ0KJycnLnN0cmlwKCkNCg0KICAgICAgICBMT0dHRVIuZGVidWcoIkZsdXjmn6Xor6Lor63lj6UgKHJhbmdlKTpcbiVzIiwgZmx1eCkNCg0KICAgICAgICB3aXRoIHdhcm5pbmdzLmNhdGNoX3dhcm5pbmdzKCk6DQogICAgICAgICAgICB3YXJuaW5ncy5zaW1wbGVmaWx0ZXIoImlnbm9yZSIsIE1pc3NpbmdQaXZvdEZ1bmN0aW9uKQ0KICAgICAgICAgICAgZnJhbWVzID0gcXVlcnlfYXBpLnF1ZXJ5X2RhdGFfZnJhbWUoZmx1eCkNCiAgICAgICAgDQogICAgICAgIGlmIGlzaW5zdGFuY2UoZnJhbWVzLCBsaXN0KToNCiAgICAgICAgICAgIGRmID0gcGQuY29uY2F0KGZyYW1lcywgaWdub3JlX2luZGV4PVRydWUpIGlmIGZyYW1lcyBlbHNlIHBkLkRhdGFGcmFtZSgpDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICBkZiA9IGZyYW1lcw0KDQogICAgICAgIGlmIGRmLmVtcHR5IG9yICdfdmFsdWUnIG5vdCBpbiBkZi5jb2x1bW5zOg0KICAgICAgICAgICAgaWYgZmllbGRfbmFtZSA9PSAi546v5aKD5rip5bqmIjoNCiAgICAgICAgICAgICAgICBMT0dHRVIuZGVidWcoIk5vIHZhbGlkIHJhbmdlIGRhdGEgZm91bmQgZm9yIGZpZWxkPSVzIChub24temVybyBkYXRhKSIsIGZpZWxkX25hbWUpDQogICAgICAgICAgICBlbHNlOg0KICAgICAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygiTm8gdmFsaWQgcmFuZ2UgZGF0YSBmb3VuZCBmb3IgZmllbGQ9JXMiLCBmaWVsZF9uYW1lKQ0KICAgICAgICAgICAgcmV0dXJuIE5vbmUNCiAgICAgICAgICAgIA0KICAgICAgICBtZWFuX3ZhbHVlID0gZGZbJ192YWx1ZSddLmlsb2NbMF0NCiAgICAgICAgaWYgcGQuaXNuYShtZWFuX3ZhbHVlKToNCiAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygiTWVhbiB2YWx1ZSBpcyBOYU4gZm9yIGZpZWxkPSVzIiwgZmllbGRfbmFtZSkNCiAgICAgICAgICAgIHJldHVybiBOb25lDQoNCiAgICAgICAgdmFsdWUgPSBmbG9hdChtZWFuX3ZhbHVlKQ0KICAgICAgICBpZiBmaWVsZF9uYW1lID09ICLnjq/looPmuKnluqYiOg0KICAgICAgICAgICAgTE9HR0VSLmRlYnVnKCJGaWVsZD0lcyByYW5nZV9tZWFuX3ZhbHVlPSUuM2YgKG5vbi16ZXJvIGRhdGEpIiwgZmllbGRfbmFtZSwgdmFsdWUpDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIkZpZWxkPSVzIHJhbmdlX21lYW5fdmFsdWU9JS4zZiIsIGZpZWxkX25hbWUsIHZhbHVlKQ0KICAgICAgICByZXR1cm4gdmFsdWUNCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6DQogICAgICAgIExPR0dFUi5lcnJvcigiRXJyb3IgcXVlcnlpbmcgSW5mbHV4REIgcmFuZ2UgZm9yIGZpZWxkPSVzOiAlcyIsIGZpZWxkX25hbWUsIGUpDQogICAgICAgIHJldHVybiBOb25lDQogICAgZmluYWxseToNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgY2xpZW50LmNsb3NlKCkNCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoNCiAgICAgICAgICAgIHBhc3MNCg0KDQpkZWYgX3F1ZXJ5X2luZmx1eGRiX3dpdGhfbG9hZF9zdGF0dXMoDQogICAgZmllbGRfbmFtZTogc3RyLA0KICAgIHRhcmdldF90aW1lOiBkYXRldGltZSwNCiAgICBpbmZsdXhfdXJsOiBzdHIsDQogICAgaW5mbHV4X29yZzogc3RyLA0KICAgIGluZmx1eF90b2tlbjogc3RyLA0KICAgIGluZmx1eF9idWNrZXQ6IHN0ciwNCiAgICBpbmZsdXhfbWVhc3VyZW1lbnQ6IHN0ciwNCiAgICBmaWx0ZXJzOiBPcHRpb25hbFtEaWN0W3N0ciwgc3RyXV0gPSBOb25lLA0KKSAtPiBPcHRpb25hbFtmbG9hdF06DQogICAgIiIi5p+l6K+iIEluZmx1eERCIOiOt+WPluaMh+WumuWtl+auteWcqOaMh+WumuaXtumXtOeCueeahOeerOaXtuWAvO+8iOS7heW9kyBsb2FkX3N0YXR1cyA9IDEg5pe277yJIiIiDQogICAgdHJ5Og0KICAgICAgICBmcm9tIGluZmx1eGRiX2NsaWVudCBpbXBvcnQgSW5mbHV4REJDbGllbnQNCiAgICAgICAgaW1wb3J0IHBhbmRhcyBhcyBwZA0KICAgICAgICBpbXBvcnQgd2FybmluZ3MNCiAgICAgICAgZnJvbSBpbmZsdXhkYl9jbGllbnQuY2xpZW50Lndhcm5pbmdzIGltcG9ydCBNaXNzaW5nUGl2b3RGdW5jdGlvbg0KICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoNCiAgICAgICAgTE9HR0VSLndhcm5pbmcoIkluZmx1eERCIGNsaWVudCBub3QgYXZhaWxhYmxlLCBza2lwIHF1ZXJ5IGZvciBmaWVsZD0lcyIsIGZpZWxkX25hbWUpDQogICAgICAgIHJldHVybiBOb25lDQoNCiAgICB0cnk6DQogICAgICAgIGNsaWVudCA9IEluZmx1eERCQ2xpZW50KHVybD1pbmZsdXhfdXJsLCBvcmc9aW5mbHV4X29yZywgdG9rZW49aW5mbHV4X3Rva2VuKQ0KICAgICAgICBxdWVyeV9hcGkgPSBjbGllbnQucXVlcnlfYXBpKCkNCg0KICAgICAgICBMT0dHRVIuZGVidWcoDQogICAgICAgICAgICAiUXVlcnlpbmcgZmllbGQ9JXMgbWVhc3VyZW1lbnQ9JXMgdGFyZ2V0X3RpbWU9JXMgZmlsdGVycz0lcyAod2l0aCBsb2FkX3N0YXR1cz0xKSIsDQogICAgICAgICAgICBmaWVsZF9uYW1lLA0KICAgICAgICAgICAgaW5mbHV4X21lYXN1cmVtZW50LA0KICAgICAgICAgICAgdGFyZ2V0X3RpbWUuc3RyZnRpbWUoJyVZLSVtLSVkVCVIOiVNOiVTWicpLA0KICAgICAgICAgICAgZmlsdGVycyBvciB7fSwNCiAgICAgICAgKQ0KDQogICAgICAgICMg5p+l6K+i6YC76L6R77ya5p+l6K+i55uu5qCH5pe26Ze054K56ZmE6L+R55qE5pWw5o2u77yM5L2G5Y+q6KaBIGxvYWRfc3RhdHVzID0gMSDnmoTmlbDmja4NCiAgICAgICAgIyDkvb/nlKjkuIDkuKrml7bpl7Tnqpflj6PmnaXmn6Xmib7mnIDmjqXov5HnmoTmnInmlYjmlbDmja7ngrkNCiAgICAgICAgd2luZG93X21pbnV0ZXMgPSAxMCAgIyDliY3lkI4xMOWIhumSn+eahOeql+WPow0KICAgICAgICANCiAgICAgICAgcXVlcnlfc3RhcnQgPSB0YXJnZXRfdGltZSAtIHRpbWVkZWx0YShtaW51dGVzPXdpbmRvd19taW51dGVzKQ0KICAgICAgICBxdWVyeV9lbmQgPSB0YXJnZXRfdGltZSArIHRpbWVkZWx0YShtaW51dGVzPXdpbmRvd19taW51dGVzKQ0KICAgICAgICANCiAgICAgICAgcXVlcnlfc3RhcnRfcmZjID0gcXVlcnlfc3RhcnQuc3RyZnRpbWUoJyVZLSVtLSVkVCVIOiVNOiVTWicpDQogICAgICAgIHF1ZXJ5X2VuZF9yZmMgPSBxdWVyeV9lbmQuc3RyZnRpbWUoJyVZLSVtLSVkVCVIOiVNOiVTWicpDQoNCiAgICAgICAgIyDmnoTlu7rov4fmu6TmnaHku7YNCiAgICAgICAgdGFnX2ZpbHRlcnMgPSAiIg0KICAgICAgICBpZiBmaWx0ZXJzOg0KICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgaW4gZmlsdGVycy5pdGVtcygpOg0KICAgICAgICAgICAgICAgIHRhZ19maWx0ZXJzICs9IGYnXG4gIHw+IGZpbHRlcihmbjogKHIpID0+IHJbIntrZXl9Il0gPT0gInt2YWx1ZX0iKScNCg0KICAgICAgICAjIOafpeivoua4qeW6puaVsOaNru+8iOS4jemcgOimgWxvYWRfc3RhdHVz562b6YCJ77yM5Zug5Li65bey57uP5Z+65LqO5pyJ5pWI5pe26Ze054K55p+l6K+i77yJDQogICAgICAgIGZsdXggPSBmJycnDQpmcm9tKGJ1Y2tldDogIntpbmZsdXhfYnVja2V0fSIpDQogIHw+IHJhbmdlKHN0YXJ0OiB7cXVlcnlfc3RhcnRfcmZjfSwgc3RvcDoge3F1ZXJ5X2VuZF9yZmN9KQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJfbWVhc3VyZW1lbnQiXSA9PSAie2luZmx1eF9tZWFzdXJlbWVudH0iKQ0KICB8PiBmaWx0ZXIoZm46IChyKSA9PiByWyJfZmllbGQiXSA9PSAie2ZpZWxkX25hbWV9Iil7dGFnX2ZpbHRlcnN9DQogIHw+IHNvcnQoY29sdW1uczogWyJfdGltZSJdKQ0KICB8PiBsYXN0KCkNCiAgfD4geWllbGQobmFtZTogImluc3RhbnRhbmVvdXNfYXRfZWZmZWN0aXZlX3RpbWUiKQ0KJycnLnN0cmlwKCkNCg0KICAgICAgICBMT0dHRVIuZGVidWcoIkZsdXjmn6Xor6Lor63lj6U6XG4lcyIsIGZsdXgpDQoNCiAgICAgICAgd2l0aCB3YXJuaW5ncy5jYXRjaF93YXJuaW5ncygpOg0KICAgICAgICAgICAgd2FybmluZ3Muc2ltcGxlZmlsdGVyKCJpZ25vcmUiLCBNaXNzaW5nUGl2b3RGdW5jdGlvbikNCiAgICAgICAgICAgIGZyYW1lcyA9IHF1ZXJ5X2FwaS5xdWVyeV9kYXRhX2ZyYW1lKGZsdXgpDQogICAgICAgIA0KICAgICAgICBpZiBpc2luc3RhbmNlKGZyYW1lcywgbGlzdCk6DQogICAgICAgICAgICBkZiA9IHBkLmNvbmNhdChmcmFtZXMsIGlnbm9yZV9pbmRleD1UcnVlKSBpZiBmcmFtZXMgZWxzZSBwZC5EYXRhRnJhbWUoKQ0KICAgICAgICBlbHNlOg0KICAgICAgICAgICAgZGYgPSBmcmFtZXMNCg0KICAgICAgICAjIOiOt+WPlueerOaXtuWAvO+8iOacgOi/keeahOS4gOS4quacieaViOaVsOaNrueCue+8iQ0KICAgICAgICBpZiBkZi5lbXB0eSBvciAnX3ZhbHVlJyBub3QgaW4gZGYuY29sdW1uczoNCiAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygiTm8gdmFsaWQgZGF0YSBmb3VuZCBmb3IgZmllbGQ9JXMgYXQgZWZmZWN0aXZlIHRpbWUgcG9pbnQiLCBmaWVsZF9uYW1lKQ0KICAgICAgICAgICAgcmV0dXJuIE5vbmUNCiAgICAgICAgICAgIA0KICAgICAgICAjIOWPluesrOS4gOihjOeahOWAvO+8iOWboOS4uuafpeivouW3sue7j+aOkuW6j+W5tuWPluS6hmxhc3QoKe+8iQ0KICAgICAgICBpbnN0YW50X3ZhbHVlID0gZGZbJ192YWx1ZSddLmlsb2NbMF0NCiAgICAgICAgaWYgcGQuaXNuYShpbnN0YW50X3ZhbHVlKToNCiAgICAgICAgICAgIExPR0dFUi5kZWJ1ZygiSW5zdGFudGFuZW91cyB2YWx1ZSBpcyBOYU4gZm9yIGZpZWxkPSVzIiwgZmllbGRfbmFtZSkNCiAgICAgICAgICAgIHJldHVybiBOb25lDQoNCiAgICAgICAgdmFsdWUgPSBmbG9hdChpbnN0YW50X3ZhbHVlKQ0KICAgICAgICANCiAgICAgICAgIyDlpoLmnpzmnInml7bpl7Tkv6Hmga/vvIzorrDlvZXlrp7pmYXnmoTmlbDmja7ml7bpl7TngrkNCiAgICAgICAgaWYgJ190aW1lJyBpbiBkZi5jb2x1bW5zOg0KICAgICAgICAgICAgYWN0dWFsX3RpbWUgPSBkZlsnX3RpbWUnXS5pbG9jWzBdDQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIkZpZWxkPSVzIGluc3RhbnRhbmVvdXNfdmFsdWU9JS4zZiBhY3R1YWxfdGltZT0lcyAoYXQgZWZmZWN0aXZlIHRpbWUpIiwgDQogICAgICAgICAgICAgICAgICAgICAgICBmaWVsZF9uYW1lLCB2YWx1ZSwgYWN0dWFsX3RpbWUpDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICBMT0dHRVIuZGVidWcoIkZpZWxkPSVzIGluc3RhbnRhbmVvdXNfdmFsdWU9JS4zZiAoYXQgZWZmZWN0aXZlIHRpbWUpIiwgZmllbGRfbmFtZSwgdmFsdWUpDQogICAgICAgICAgICANCiAgICAgICAgcmV0dXJuIHZhbHVlDQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOg0KICAgICAgICBMT0dHRVIuZXJyb3IoIkVycm9yIHF1ZXJ5aW5nIEluZmx1eERCIGZvciBmaWVsZD0lczogJXMiLCBmaWVsZF9uYW1lLCBlKQ0KICAgICAgICByZXR1cm4gTm9uZQ0KICAgIGZpbmFsbHk6DQogICAgICAgIHRyeToNCiAgICAgICAgICAgIGNsaWVudC5jbG9zZSgpDQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb246DQogICAgICAgICAgICBwYXNzDQoNCg0KZGVmIF9sb2FkX3RlbXBlcmF0dXJlX2RhdGFfd2l0aF9sb2FkX3N0YXR1cygNCiAgICB0aW1lX3Nsb3RzOiBMaXN0W3N0cl0sDQogICAgc2VjdGlvbnM6IExpc3RbRGljdFtzdHIsIEFueV1dLA0KICAgIHN0YXJ0X3RpbWU6IE9wdGlvbmFsW2RhdGV0aW1lXSwNCiAgICBlbmRfdGltZTogT3B0aW9uYWxbZGF0ZXRpbWVdLA0KKSAtPiBEaWN0W3N0ciwgRGljdFtzdHIsIGZsb2F0XV06DQogICAgIiIi5LuOIEluZmx1eERCIOafpeivouaJgOaciea1i+ivlemDqOS9jeWcqOWQhOaXtumXtOeCueeahOeerOaXtua4qeW6puWAvO+8iOS7heW9kyBsb2FkX3N0YXR1cyA9IDEg5pe277yJIiIiDQogICAgaWYgbm90IHN0YXJ0X3RpbWUgb3Igbm90IGVuZF90aW1lOg0KICAgICAgICBMT0dHRVIuaW5mbygiU2tpcCBkYXRhIHF1ZXJ5OiBtaXNzaW5nIHN0YXJ0L2VuZCAoJXMsICVzKSIsIHN0YXJ0X3RpbWUsIGVuZF90aW1lKQ0KICAgICAgICByZXR1cm4ge30NCiAgICANCiAgICBpbmZsdXhfY29uZmlnID0gX2dldF9pbmZsdXhfY29uZmlnKCkNCiAgICANCiAgICBpZiBub3QgYWxsKFtpbmZsdXhfY29uZmlnWyd1cmwnXSwgaW5mbHV4X2NvbmZpZ1snb3JnJ10sIGluZmx1eF9jb25maWdbJ3Rva2VuJ10sIA0KICAgICAgICAgICAgICAgIGluZmx1eF9jb25maWdbJ2J1Y2tldCddLCBpbmZsdXhfY29uZmlnWydtZWFzdXJlbWVudCddXSk6DQogICAgICAgIExPR0dFUi53YXJuaW5nKA0KICAgICAgICAgICAgIlNraXAgZGF0YSBxdWVyeTogbWlzc2luZyBJbmZsdXggY29uZmlnIHVybD0lcyBidWNrZXQ9JXMgbWVhc3VyZW1lbnQ9JXMiLA0KICAgICAgICAgICAgaW5mbHV4X2NvbmZpZ1sndXJsJ10gb3IgIjxlbXB0eT4iLA0KICAgICAgICAgICAgaW5mbHV4X2NvbmZpZ1snYnVja2V0J10gb3IgIjxlbXB0eT4iLA0KICAgICAgICAgICAgaW5mbHV4X2NvbmZpZ1snbWVhc3VyZW1lbnQnXSBvciAiPGVtcHR5PiIsDQogICAgICAgICkNCiAgICAgICAgcmV0dXJuIHt9DQogICAgDQogICAgIyDorqHnrpfmgLvml7bplb/vvIjlsI/ml7bvvIkNCiAgICB0b3RhbF9kdXJhdGlvbiA9IChlbmRfdGltZSAtIHN0YXJ0X3RpbWUpLnRvdGFsX3NlY29uZHMoKSAvIDM2MDAuMA0KICAgIExPR0dFUi5pbmZvKA0KICAgICAgICAiRmV0Y2ggaW5zdGFudGFuZW91cyB0ZW1wZXJhdHVyZSBkYXRhIChsb2FkX3N0YXR1cz0xKSB3aW5kb3c9JXPihpIlcyB0b3RhbF9ob3Vycz0lLjNmIHRpbWVfcG9pbnRzPSVzIiwNCiAgICAgICAgc3RhcnRfdGltZS5pc29mb3JtYXQoKSwNCiAgICAgICAgZW5kX3RpbWUuaXNvZm9ybWF0KCksDQogICAgICAgIHRvdGFsX2R1cmF0aW9uLA0KICAgICAgICAiLCIuam9pbih0aW1lX3Nsb3RzKSwNCiAgICApDQogICAgDQogICAgIyDmlLbpm4bmiYDmnInpnIDopoHmn6Xor6LnmoTlrZfmrrUNCiAgICBxdWVyeV90YXJnZXRzOiBMaXN0W3R1cGxlW3N0ciwgRGljdFtzdHIsIEFueV1dXSA9IFtdDQogICAgZm9yIHNlY3Rpb24gaW4gc2VjdGlvbnM6DQogICAgICAgIGVudHJpZXMgPSBzZWN0aW9uLmdldCgiZW50cmllcyIpIG9yIFtdDQogICAgICAgIGZvciBlbnRyeSBpbiBlbnRyaWVzOg0KICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShlbnRyeSwgZGljdCk6DQogICAgICAgICAgICAgICAgZmllbGRfbmFtZSA9IGVudHJ5LmdldCgiZmllbGQiLCAiIikNCiAgICAgICAgICAgICAgICBpZiBmaWVsZF9uYW1lOg0KICAgICAgICAgICAgICAgICAgICBxdWVyeV90YXJnZXRzLmFwcGVuZCgoZmllbGRfbmFtZSwgZW50cnkpKQ0KDQogICAgaWYgbm90IHF1ZXJ5X3RhcmdldHM6DQogICAgICAgIHJldHVybiB7fQ0KICAgIA0KICAgICMg6K6h566X5Z+65LqO5pyJ5pWI6L+Q6KGM5pe26Ze057Sv6K6h55qE55yf5a6e5pe26Ze054K5DQogICAgTE9HR0VSLmluZm8oIj09PSDlvIDlp4vorqHnrpfmnInmlYjml7bpl7TngrkgPT09IikNCiAgICBlZmZlY3RpdmVfdGltZV9wb2ludHMgPSBfY2FsY3VsYXRlX2VmZmVjdGl2ZV90aW1lX3BvaW50cygNCiAgICAgICAgc3RhcnRfdGltZSwgZW5kX3RpbWUsIHRpbWVfc2xvdHMsIGluZmx1eF9jb25maWcNCiAgICApDQogICAgDQogICAgIyDkuLrmr4/kuKrmnInmlYjml7bpl7Tngrnmn6Xor6LmuKnluqbmlbDmja4NCiAgICB0ZW1wZXJhdHVyZV9kYXRhOiBEaWN0W3N0ciwgRGljdFtzdHIsIGZsb2F0XV0gPSB7fQ0KICAgIA0KICAgIGZvciBpZHgsIHNsb3Rfc3RyIGluIGVudW1lcmF0ZSh0aW1lX3Nsb3RzKToNCiAgICAgICAgdGFyZ2V0X3RpbWVfcG9pbnQgPSBlZmZlY3RpdmVfdGltZV9wb2ludHMuZ2V0KHNsb3Rfc3RyKQ0KICAgICAgICANCiAgICAgICAgaWYgdGFyZ2V0X3RpbWVfcG9pbnQgaXMgTm9uZToNCiAgICAgICAgICAgIExPR0dFUi53YXJuaW5nKCJObyBlZmZlY3RpdmUgdGltZSBwb2ludCBjYWxjdWxhdGVkIGZvciBzbG90ICVzLCBza2lwcGluZyIsIHNsb3Rfc3RyKQ0KICAgICAgICAgICAgY29udGludWUNCiAgICAgICAgDQogICAgICAgIExPR0dFUi5kZWJ1ZygiUHJvY2Vzc2luZyBzbG90ICVzIGF0IGVmZmVjdGl2ZSB0aW1lIHBvaW50ICVzIiwgDQogICAgICAgICAgICAgICAgICAgIHNsb3Rfc3RyLCB0YXJnZXRfdGltZV9wb2ludC5zdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnKSkNCiAgICAgICAgDQogICAgICAgIGZvciBmaWVsZF9uYW1lLCBlbnRyeSBpbiBxdWVyeV90YXJnZXRzOg0KICAgICAgICAgICAgcmVzdWx0X2tleSA9IGVudHJ5LmdldCgicmVzdWx0X2tleSIpIG9yIGZpZWxkX25hbWUNCiAgICAgICAgICAgIGlmIG5vdCByZXN1bHRfa2V5Og0KICAgICAgICAgICAgICAgIHJlc3VsdF9rZXkgPSBmaWVsZF9uYW1lDQogICAgICAgICAgICBlbnRyeV9maWx0ZXJzID0gZW50cnkuZ2V0KCJmaWx0ZXJzIikgaWYgaXNpbnN0YW5jZShlbnRyeSwgZGljdCkgZWxzZSBOb25lDQogICAgICAgICAgICBpZiByZXN1bHRfa2V5IG5vdCBpbiB0ZW1wZXJhdHVyZV9kYXRhOg0KICAgICAgICAgICAgICAgIHRlbXBlcmF0dXJlX2RhdGFbcmVzdWx0X2tleV0gPSB7fQ0KDQogICAgICAgICAgICAjIOS9v+eUqOe0ouW8leS9nOS4umtlee+8jOWboOS4uuWPr+iDveaciemHjeWkjeeahOaXtumXtOWIu+W6pg0KICAgICAgICAgICAgc2xvdF9rZXkgPSBmIntpZHh9X3tzbG90X3N0cn0iICAjIOS9v+eUqOe0ouW8lSvml7bpl7TliLvluqbkvZzkuLrllK/kuIBrZXkNCg0KICAgICAgICAgICAgIyDmn6Xor6Lnnqzml7blgLzvvIjlnKjmnInmlYjml7bpl7TngrnvvIkNCiAgICAgICAgICAgIHZhbHVlID0gX3F1ZXJ5X2luZmx1eGRiX3dpdGhfbG9hZF9zdGF0dXMoDQogICAgICAgICAgICAgICAgZmllbGRfbmFtZSwNCiAgICAgICAgICAgICAgICB0YXJnZXRfdGltZV9wb2ludCwNCiAgICAgICAgICAgICAgICBpbmZsdXhfY29uZmlnWyd1cmwnXSwNCiAgICAgICAgICAgICAgICBpbmZsdXhfY29uZmlnWydvcmcnXSwNCiAgICAgICAgICAgICAgICBpbmZsdXhfY29uZmlnWyd0b2tlbiddLA0KICAgICAgICAgICAgICAgIGluZmx1eF9jb25maWdbJ2J1Y2tldCddLA0KICAgICAgICAgICAgICAgIGluZmx1eF9jb25maWdbJ21lYXN1cmVtZW50J10sDQogICAgICAgICAgICAgICAgZmlsdGVycz1lbnRyeV9maWx0ZXJzIGlmIGVudHJ5X2ZpbHRlcnMgZWxzZSBOb25lLA0KICAgICAgICAgICAgKQ0KDQogICAgICAgICAgICBpZiB2YWx1ZSBpcyBub3QgTm9uZToNCiAgICAgICAgICAgICAgICB0ZW1wZXJhdHVyZV9kYXRhW3Jlc3VsdF9rZXldW3Nsb3Rfa2V5XSA9IHZhbHVlDQogICAgICAgICAgICAgICAgTE9HR0VSLmRlYnVnKA0KICAgICAgICAgICAgICAgICAgICAiU2xvdD0lcyBmaWVsZD0lcyB2YWx1ZT0lLjNmIGF0IGVmZmVjdGl2ZV90aW1lPSVzIiwNCiAgICAgICAgICAgICAgICAgICAgc2xvdF9rZXksDQogICAgICAgICAgICAgICAgICAgIHJlc3VsdF9rZXksDQogICAgICAgICAgICAgICAgICAgIHZhbHVlLA0KICAgICAgICAgICAgICAgICAgICB0YXJnZXRfdGltZV9wb2ludC5zdHJmdGltZSgnJUg6JU06JVMnKQ0KICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgIGVsc2U6DQogICAgICAgICAgICAgICAgTE9HR0VSLmRlYnVnKA0KICAgICAgICAgICAgICAgICAgICAiU2xvdD0lcyBmaWVsZD0lcyBub19kYXRhIGF0IGVmZmVjdGl2ZV90aW1lPSVzIiwNCiAgICAgICAgICAgICAgICAgICAgc2xvdF9rZXksDQogICAgICAgICAgICAgICAgICAgIHJlc3VsdF9rZXksDQogICAgICAgICAgICAgICAgICAgIHRhcmdldF90aW1lX3BvaW50LnN0cmZ0aW1lKCclSDolTTolUycpDQogICAgICAgICAgICAgICAgKQ0KDQogICAgcmV0dXJuIHRlbXBlcmF0dXJlX2RhdGENCg0KDQpkZWYgX2J1aWxkX2NlbGxzX3dpdGhfbG9hZF9zdGF0dXMoDQogICAgdGltZV9zbG90czogTGlzdFtzdHJdLA0KICAgIHNlY3Rpb25zOiBMaXN0W0RpY3Rbc3RyLCBBbnldXSwNCiAgICBtb3Rvcl9zcGVlZDogc3RyLA0KICAgIHN0YXJ0X3RpbWU6IE9wdGlvbmFsW2RhdGV0aW1lXSwNCiAgICBlbmRfdGltZTogT3B0aW9uYWxbZGF0ZXRpbWVdLA0KICAgIHRlbXBlcmF0dXJlX2RhdGE6IERpY3Rbc3RyLCBEaWN0W3N0ciwgZmxvYXRdXSwNCiAgICB1c2VfZGVmYXVsdHM6IGJvb2wgPSBGYWxzZSwNCikgLT4gTGlzdFtEaWN0W3N0ciwgQW55XV06DQogICAgIiIi5p6E5bu65Y2V5YWD5qC85pWw5o2u77yI5Z+65LqOIGxvYWRfc3RhdHVzID0gMSDnmoTmnInmlYjmlbDmja7vvIktIOS4juWOn+Wni+iEmuacrOe7k+aehOWujOWFqOS4gOiHtCIiIg0KICAgIGNlbGxzOiBMaXN0W0RpY3Rbc3RyLCBBbnldXSA9IFtdDQoNCiAgICBkZWYgYWRkX2NlbGwocm93OiBpbnQsIGNvbDogaW50LCB2YWx1ZTogc3RyID0gIiIsIHJvd3NwYW46IGludCA9IDEsIGNvbHNwYW46IGludCA9IDEpIC0+IE5vbmU6DQogICAgICAgIHBheWxvYWQ6IERpY3Rbc3RyLCBBbnldID0geyJyb3ciOiByb3csICJjb2wiOiBjb2wsICJ2YWx1ZSI6IHZhbHVlfQ0KICAgICAgICBpZiByb3dzcGFuID4gMToNCiAgICAgICAgICAgIHBheWxvYWRbInJvd3NwYW4iXSA9IHJvd3NwYW4NCiAgICAgICAgaWYgY29sc3BhbiA+IDE6DQogICAgICAgICAgICBwYXlsb2FkWyJjb2xzcGFuIl0gPSBjb2xzcGFuDQogICAgICAgIGNlbGxzLmFwcGVuZChwYXlsb2FkKQ0KDQogICAgIyDmqKHmnb/lt6bkvqfmoIfpopjliJflt7Lnu4/ljrvpmaTvvIzov5nph4zku4XnlJ/miJDnuq/mlbDmja7ljLrvvIzku44gKDAsMCkg5byA5aeL5aGr5YWl5pWw5YC844CCDQogICAgIyBjdXJyZW50X3JvdyDlr7nlupTmqKHmnb/kuK3nmoTlrp7pmYXmlbDmja7ooYzntKLlvJXjgIINCiAgICBjdXJyZW50X3JvdyA9IDANCiAgICBmb3Igc2VjdGlvbiBpbiBzZWN0aW9uczoNCiAgICAgICAgZW50cmllcyA9IHNlY3Rpb24uZ2V0KCJlbnRyaWVzIikgb3IgW10NCiAgICAgICAgaWYgbm90IGVudHJpZXM6DQogICAgICAgICAgICBjb250aW51ZQ0KICAgICAgICAjIOavj+S4qua1i+ivlemDqOS9jeWtkOmhueWvueW6lOaooeadv+S4reeahOS4gOihjA0KICAgICAgICBmb3IgZW50cnkgaW4gZW50cmllczoNCiAgICAgICAgICAgICMg5pSv5oyB5paw5qC85byP77yI5bimIGZpZWxkIOaYoOWwhO+8ieWSjOaXp+agvOW8j++8iOe6r+Wtl+espuS4su+8iQ0KICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShlbnRyeSwgZGljdCk6DQogICAgICAgICAgICAgICAgZmllbGRfbmFtZSA9IGVudHJ5LmdldCgiZmllbGQiLCAiIikNCiAgICAgICAgICAgICAgICBlbnRyeV9maWx0ZXJzID0gZW50cnkuZ2V0KCJmaWx0ZXJzIikNCiAgICAgICAgICAgICAgICBlbnRyeV9rZXkgPSBlbnRyeS5nZXQoInJlc3VsdF9rZXkiKSBvciBmaWVsZF9uYW1lDQogICAgICAgICAgICBlbHNlOg0KICAgICAgICAgICAgICAgIGZpZWxkX25hbWUgPSAiIg0KICAgICAgICAgICAgICAgIGVudHJ5X2ZpbHRlcnMgPSBOb25lDQogICAgICAgICAgICAgICAgZW50cnlfa2V5ID0gIiINCg0KICAgICAgICAgICAgIyDku4XovpPlh7rmlbDlgLzliJfvvJrliJfntKLlvJXnm7TmjqXlr7nlupTml7bpl7TmrrUNCiAgICAgICAgICAgICMg5by65Yi25aGr5YWF5omA5pyJ5YiX77yM5LyY5YWI5L2/55So5p+l6K+i5pWw5o2u77yM5ZCm5YiZ5L2/55So6buY6K6k5YC8DQogICAgICAgICAgICBpZiBmaWVsZF9uYW1lOg0KICAgICAgICAgICAgICAgIHRhcmdldF9rZXkgPSBlbnRyeV9rZXkgb3IgZmllbGRfbmFtZQ0KDQogICAgICAgICAgICAgICAgIyDpgY3ljobmiYDmnInml7bpl7TmrrXliJfvvIznoa7kv53mr4/kuIDliJfpg73mnInmlbDmja4NCiAgICAgICAgICAgICAgICBmb3IgY29sX2lkeCwgc2xvdCBpbiBlbnVtZXJhdGUodGltZV9zbG90cyk6DQogICAgICAgICAgICAgICAgICAgIHZhbHVlID0gTm9uZQ0KDQogICAgICAgICAgICAgICAgICAgICMg5LyY5YWI5L2/55So5p+l6K+i5Yiw55qE5pWw5o2uDQogICAgICAgICAgICAgICAgICAgIGlmIHRlbXBlcmF0dXJlX2RhdGE6DQogICAgICAgICAgICAgICAgICAgICAgICBzbG90X2RhdGEgPSB0ZW1wZXJhdHVyZV9kYXRhLmdldCh0YXJnZXRfa2V5LCB7fSkNCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIHNsb3RfZGF0YToNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbG90X2tleSA9IGYie2NvbF9pZHh9X3tzbG90fSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IHNsb3RfZGF0YS5nZXQoc2xvdF9rZXkpDQoNCiAgICAgICAgICAgICAgICAgICAgaWYgdmFsdWUgaXMgTm9uZSBhbmQgdXNlX2RlZmF1bHRzOg0KICAgICAgICAgICAgICAgICAgICAgICAgIyDkvb/nlKjln7rnoYDpu5jorqTlgLwgKyDml7bpl7TmrrXlgY/np7vvvIjmr4/kuKrml7bpl7TmrrXlop7liqAwLjHluqbvvIkNCiAgICAgICAgICAgICAgICAgICAgICAgIGRlZmF1bHRfYmFzZV92YWx1ZSA9IDI1LjAgICMg566A5YyW55qE6buY6K6k5YC8DQogICAgICAgICAgICAgICAgICAgICAgICB0aW1lX29mZnNldCA9IGNvbF9pZHggKiAwLjENCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gZGVmYXVsdF9iYXNlX3ZhbHVlICsgdGltZV9vZmZzZXQNCg0KICAgICAgICAgICAgICAgICAgICBpZiB2YWx1ZSBpcyBOb25lOg0KICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfc3RyID0gIiINCiAgICAgICAgICAgICAgICAgICAgZWxzZToNCiAgICAgICAgICAgICAgICAgICAgICAgICMg5qC85byP5YyW5Li65a2X56ym5Liy77yI5L+d55WZMeS9jeWwj+aVsO+8iQ0KICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfc3RyID0gZiJ7dmFsdWU6LjFmfSINCg0KICAgICAgICAgICAgICAgICAgICBhZGRfY2VsbChjdXJyZW50X3JvdywgY29sX2lkeCwgdmFsdWVfc3RyKQ0KICAgICAgICAgICAgZWxzZToNCiAgICAgICAgICAgICAgICAjIOWmguaenOayoeacieWtl+auteWQje+8jOWhq+WFheepuuWtl+espuS4sg0KICAgICAgICAgICAgICAgIGZvciBjb2xfaWR4IGluIHJhbmdlKGxlbih0aW1lX3Nsb3RzKSk6DQogICAgICAgICAgICAgICAgICAgIGFkZF9jZWxsKGN1cnJlbnRfcm93LCBjb2xfaWR4LCAiIikNCiAgICAgICAgICAgIGN1cnJlbnRfcm93ICs9IDENCg0KICAgIHJldHVybiBjZWxscw0KDQoNCmRlZiBidWlsZF90ZW1wZXJhdHVyZV90YWJsZV93aXRoX2xvYWRfc3RhdHVzKF86IERpY3Rbc3RyLCBBbnldKSAtPiBEaWN0W3N0ciwgQW55XToNCiAgICAiIiLmnoTlu7rmuKnluqbooajmoLzmlbDmja7vvIjku4Xkvb/nlKggbG9hZF9zdGF0dXMgPSAxIOeahOacieaViOaVsOaNru+8iSIiIg0KICAgIF9zZXR1cF9sb2dnaW5nKCkNCiAgICANCiAgICB0b2tlbiA9IG9zLmVudmlyb24uZ2V0KCJUQUJMRV9UT0tFTiIsICJzY3JpcHRUYWJsZTEiKQ0KICAgIHJvd19vZmZzZXQgPSBpbnQob3MuZW52aXJvbi5nZXQoIlRBQkxFX1NUQVJUX1JPVyIsICIwIikgb3IgMCkNCiAgICBjb2xfb2Zmc2V0ID0gaW50KG9zLmVudmlyb24uZ2V0KCJUQUJMRV9TVEFSVF9DT0wiLCAiMCIpIG9yIDApDQogICAgbW90b3Jfc3BlZWQgPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfTU9UT1JfU1BFRUQiLCAiOTgwUlBNIikNCiAgICANCiAgICAjIOino+aekOWunumqjOaXtumXtOiMg+WbtA0KICAgIHN0YXJ0X3RpbWUsIGVuZF90aW1lID0gX3BhcnNlX2V4cGVyaW1lbnRfdGltZXMoKQ0KICAgIA0KICAgIHRpbWVfc2xvdHMgPSBfdGltZV9zbG90cygpDQogICAgc2VjdGlvbnMgPSBfZGVmYXVsdF9zZWN0aW9ucygpDQogICAgDQogICAgIyDmn6Xor6LmuKnluqbmlbDmja7vvIjku4XlvZMgbG9hZF9zdGF0dXMgPSAxIOaXtu+8iQ0KICAgIHRlbXBlcmF0dXJlX2RhdGEgPSBfbG9hZF90ZW1wZXJhdHVyZV9kYXRhX3dpdGhfbG9hZF9zdGF0dXModGltZV9zbG90cywgc2VjdGlvbnMsIHN0YXJ0X3RpbWUsIGVuZF90aW1lKQ0KICAgIA0KICAgICMg5aeL57uI56aB5q2i6buY6K6k5pWw5o2u77yM5L+d6K+B5p+l6K+i5LiN5Yiw5YC85pe25L+d5oyB56m655m9DQogICAgdXNlX2RlZmF1bHRzID0gRmFsc2UNCiAgICANCiAgICBjZWxscyA9IF9idWlsZF9jZWxsc193aXRoX2xvYWRfc3RhdHVzKA0KICAgICAgICB0aW1lX3Nsb3RzLCANCiAgICAgICAgc2VjdGlvbnMsIA0KICAgICAgICBtb3Rvcl9zcGVlZCwgDQogICAgICAgIHN0YXJ0X3RpbWUsIA0KICAgICAgICBlbmRfdGltZSwgDQogICAgICAgIHRlbXBlcmF0dXJlX2RhdGEsDQogICAgICAgIHVzZV9kZWZhdWx0cz11c2VfZGVmYXVsdHMNCiAgICApDQogICAgDQogICAgIyDlupTnlKjooYzlgY/np7sNCiAgICBmb3IgY2VsbCBpbiBjZWxsczoNCiAgICAgICAgY2VsbFsicm93Il0gKz0gNA0KICAgIA0KICAgICMg5re75Yqg5a6e6aqM5pe26Ze05L+h5oGv77yI5LiO5Y6f5aeL6ISa5pys5a6M5YWo5LiA6Ie055qE6YC76L6R77yJDQogICAgc3RhcnRfdGltZV9yb3cgPSAxDQogICAgc3RhcnRfdGltZV92YWx1ZV9jb2wgPSAxDQogICAgZW5kX3RpbWVfdmFsdWVfY29sID0gMw0KICAgIA0KICAgICMg6I635Y+W5Y6f5aeL5pe26Ze05a2X56ym5Liy6L+b6KGM5aSE55CG77yI5LiO5Y6f5aeL6ISa5pys5L+d5oyB5LiA6Ie077yJDQogICAgc3RhcnRfc3RyID0gb3MuZW52aXJvbi5nZXQoIkVYUEVSSU1FTlRfU1RBUlQiLCAiIikuc3RyaXAoKQ0KICAgIGlmIHN0YXJ0X3N0ciBhbmQgc3RhcnRfdGltZToNCiAgICAgICAgdHJ5Og0KICAgICAgICAgICAgIyDkvb/nlKjkuI7ljp/lp4vohJrmnKznm7jlkIznmoTml7bpl7TlpITnkIbpgLvovpENCiAgICAgICAgICAgIHV0Y19hd2FyZV9kdCA9IGRhdGV0aW1lLnN0cnB0aW1lKHN0YXJ0X3N0ciwgIiVZLSVtLSVkVCVIOiVNOiVTJXoiKQ0KICAgICAgICAgICAgbG9jYWxfZHQxID0gdXRjX2F3YXJlX2R0LmFzdGltZXpvbmUodHo9Tm9uZSkNCiAgICAgICAgICAgIGxvY2FsX2R0MiA9IHV0Y19hd2FyZV9kdC5hc3RpbWV6b25lKHR6PU5vbmUpICsgdGltZWRlbHRhKGhvdXJzPTMuNSkNCiAgICAgICAgICAgIHN0YXJ0X3RpbWVfdmFsdWUgPSBsb2NhbF9kdDEuc3RyZnRpbWUoIiVZLSVtLSVkICVIOiVNOiVTIikNCiAgICAgICAgICAgIGVuZF90aW1lX3ZhbHVlID0gbG9jYWxfZHQyLnN0cmZ0aW1lKCIlWS0lbS0lZCAlSDolTTolUyIpDQogICAgICAgICAgICBjZWxscy5hcHBlbmQoeyJyb3ciOiBzdGFydF90aW1lX3JvdywgImNvbCI6IHN0YXJ0X3RpbWVfdmFsdWVfY29sLCAidmFsdWUiOiBzdGFydF90aW1lX3ZhbHVlfSkNCiAgICAgICAgICAgIGNlbGxzLmFwcGVuZCh7InJvdyI6IHN0YXJ0X3RpbWVfcm93LCAiY29sIjogZW5kX3RpbWVfdmFsdWVfY29sLCAidmFsdWUiOiBlbmRfdGltZV92YWx1ZX0pDQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToNCiAgICAgICAgICAgIExPR0dFUi53YXJuaW5nKCJGYWlsZWQgdG8gcHJvY2VzcyBleHBlcmltZW50IHRpbWUgc3RyaW5nczogJXMiLCBlKQ0KICAgIA0KICAgICMg5p+l6K+i546v5aKD5rip5bqm77yI5LiO5Y6f5aeL6ISa5pys5a6M5YWo5LiA6Ie055qE6YC76L6R77yJDQogICAgaW5mbHV4X3VybCA9IG9zLmVudmlyb24uZ2V0KCJJTkZMVVhfVVJMIiwgIiIpLnN0cmlwKCkNCiAgICBpbmZsdXhfb3JnID0gb3MuZW52aXJvbi5nZXQoIklORkxVWF9PUkciLCAiIikuc3RyaXAoKQ0KICAgIGluZmx1eF90b2tlbiA9IG9zLmVudmlyb24uZ2V0KCJJTkZMVVhfVE9LRU4iLCAiIikuc3RyaXAoKQ0KICAgIGluZmx1eF9idWNrZXQgPSBvcy5lbnZpcm9uLmdldCgiSU5GTFVYX0JVQ0tFVCIsICJQQ00iKS5zdHJpcCgpDQogICAgaW5mbHV4X21lYXN1cmVtZW50ID0gb3MuZW52aXJvbi5nZXQoIklORkxVWF9NRUFTVVJFTUVOVCIsICJQQ01fTWVhc3VyZW1lbnQiKS5zdHJpcCgpDQogICAgDQogICAgaWYgc3RhcnRfdGltZSBhbmQgZW5kX3RpbWU6DQogICAgICAgICMg5a+55LqO546v5aKD5rip5bqm77yM5L2/55So5pe26Ze06IyD5Zu05p+l6K+i77yI5LiO5Y6f5aeL6ISa5pys6YC76L6R5LiA6Ie077yJDQogICAgICAgIHZhbHVlID0gX3F1ZXJ5X2luZmx1eGRiX3JhbmdlX3dpdGhfbG9hZF9zdGF0dXMoDQogICAgICAgICAgICAi546v5aKD5rip5bqmIiwNCiAgICAgICAgICAgIHN0YXJ0X3RpbWUsDQogICAgICAgICAgICBlbmRfdGltZSwNCiAgICAgICAgICAgIGluZmx1eF91cmwsDQogICAgICAgICAgICBpbmZsdXhfb3JnLA0KICAgICAgICAgICAgaW5mbHV4X3Rva2VuLA0KICAgICAgICAgICAgaW5mbHV4X2J1Y2tldCwNCiAgICAgICAgICAgIGluZmx1eF9tZWFzdXJlbWVudCwNCiAgICAgICAgICAgIGZpbHRlcnM9eyJkYXRhX3R5cGUiOiAiTFNEQVEifSwNCiAgICAgICAgKQ0KICAgICAgICAjIOehruS/nXZhbHVl5LiN5pivTm9uZe+8jOmBv+WFjVdvcmQgQ09N5pON5L2c5byC5bi477yI5LiO5Y6f5aeL6ISa5pys5LiA6Ie077yJDQogICAgICAgIGlmIHZhbHVlIGlzIG5vdCBOb25lOg0KICAgICAgICAgICAgY2VsbHMuYXBwZW5kKHsicm93IjogMCwgImNvbCI6IDEsICJ2YWx1ZSI6IGYie3ZhbHVlOi4xZn0ifSkNCiAgICAgICAgZWxzZToNCiAgICAgICAgICAgIGNlbGxzLmFwcGVuZCh7InJvdyI6IDAsICJjb2wiOiAxLCAidmFsdWUiOiAiIn0pDQogICAgDQogICAgTE9HR0VSLmluZm8oDQogICAgICAgICJUZW1wZXJhdHVyZSB0YWJsZSBidWlsdCB3aXRoIGxvYWRfc3RhdHVzPTEgZmlsdGVyOiB0b2tlbj0lcyBjZWxscz0lZCB0aW1lX3Nsb3RzPSVzIiwNCiAgICAgICAgdG9rZW4sDQogICAgICAgIGxlbihjZWxscyksDQogICAgICAgICIsIi5qb2luKHRpbWVfc2xvdHMpLA0KICAgICkNCiAgICANCiAgICByZXR1cm4gew0KICAgICAgICAidG9rZW4iOiB0b2tlbiwNCiAgICAgICAgInN0YXJ0Um93Ijogcm93X29mZnNldCwNCiAgICAgICAgInN0YXJ0Q29sIjogY29sX29mZnNldCwNCiAgICAgICAgImNlbGxzIjogY2VsbHMsDQogICAgfQ0KDQoNCmRlZiBfbG9hZF9wYXlsb2FkKCkgLT4gRGljdFtzdHIsIEFueV06DQogICAgIiIi5LuO5qCH5YeG6L6T5YWl5oiW546v5aKD5Y+Y6YeP5Yqg6L29cGF5bG9hZOaVsOaNriIiIg0KICAgIHRyeToNCiAgICAgICAgIyDlsJ3or5Xku47moIflh4bovpPlhaXor7vlj5ZKU09ODQogICAgICAgIHRyeToNCiAgICAgICAgICAgIGltcG9ydCBzZWxlY3QNCiAgICAgICAgICAgIGlmIHNlbGVjdC5zZWxlY3QoW3N5cy5zdGRpbl0sIFtdLCBbXSwgMC4wKVswXToNCiAgICAgICAgICAgICAgICBwYXlsb2FkX3N0ciA9IHN5cy5zdGRpbi5yZWFkKCkuc3RyaXAoKQ0KICAgICAgICAgICAgICAgIGlmIHBheWxvYWRfc3RyOg0KICAgICAgICAgICAgICAgICAgICByZXR1cm4ganNvbi5sb2FkcyhwYXlsb2FkX3N0cikNCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yOg0KICAgICAgICAgICAgIyBXaW5kb3dz5LiKc2VsZWN05Y+v6IO95LiN5Y+v55So77yM5bCd6K+V55u05o6l6K+75Y+WDQogICAgICAgICAgICBpbXBvcnQgbXN2Y3J0DQogICAgICAgICAgICBpZiBtc3ZjcnQua2JoaXQoKToNCiAgICAgICAgICAgICAgICBwYXlsb2FkX3N0ciA9IHN5cy5zdGRpbi5yZWFkKCkuc3RyaXAoKQ0KICAgICAgICAgICAgICAgIGlmIHBheWxvYWRfc3RyOg0KICAgICAgICAgICAgICAgICAgICByZXR1cm4ganNvbi5sb2FkcyhwYXlsb2FkX3N0cikNCiAgICBleGNlcHQgRXhjZXB0aW9uOg0KICAgICAgICBwYXNzDQogICAgDQogICAgIyDlpoLmnpzmsqHmnInmoIflh4bovpPlhaXvvIzov5Tlm57nqbrlrZflhbgNCiAgICByZXR1cm4ge30NCg0KDQpkZWYgX2xvZ19lbnZpcm9ubWVudF92YXJpYWJsZXMoKSAtPiBOb25lOg0KICAgICIiIuiusOW9leebuOWFs+eOr+Wig+WPmOmHjyIiIg0KICAgIGVudl92YXJzID0gWw0KICAgICAgICAiVEFCTEVfVE9LRU4iLCAiVEFCTEVfU1RBUlRfUk9XIiwgIlRBQkxFX1NUQVJUX0NPTCIsICJUQUJMRV9USU1FX1NMT1RTIiwgIlRBQkxFX01PVE9SX1NQRUVEIiwNCiAgICAgICAgIkVYUEVSSU1FTlRfU1RBUlQiLCAiRVhQRVJJTUVOVF9FTkQiLA0KICAgICAgICAiSU5GTFVYX1VSTCIsICJJTkZMVVhfT1JHIiwgIklORkxVWF9UT0tFTiIsICJJTkZMVVhfQlVDS0VUIiwgIklORkxVWF9NRUFTVVJFTUVOVCINCiAgICBdDQogICAgDQogICAgZm9yIHZhciBpbiBlbnZfdmFyczoNCiAgICAgICAgdmFsdWUgPSBvcy5lbnZpcm9uLmdldCh2YXIsICIiKQ0KICAgICAgICBpZiAiVE9LRU4iIGluIHZhciBhbmQgdmFsdWU6DQogICAgICAgICAgICB2YWx1ZSA9IF9tYXNrX3NlY3JldCh2YWx1ZSkNCiAgICAgICAgTE9HR0VSLmRlYnVnKCJFTlYgJXM9JXMiLCB2YXIsIHZhbHVlIG9yICI8ZW1wdHk+IikNCg0KDQpkZWYgbWFpbigpIC0+IGludDoNCiAgICB0cnk6DQogICAgICAgIHRyeToNCiAgICAgICAgICAgIGlmIG5vdCBsb2dnaW5nLmdldExvZ2dlcigpLmhhbmRsZXJzOg0KICAgICAgICAgICAgICAgIGxvZ19sZXZlbF9uYW1lID0gb3MuZW52aXJvbi5nZXQoIlRBQkxFX0xPR19MRVZFTCIsICJERUJVRyIpLnN0cmlwKCkgb3IgIkRFQlVHIg0KICAgICAgICAgICAgICAgIGxvZ19sZXZlbCA9IGdldGF0dHIobG9nZ2luZywgbG9nX2xldmVsX25hbWUudXBwZXIoKSwgbG9nZ2luZy5ERUJVRykNCiAgICAgICAgICAgICAgICBsb2dfZmlsZV9yYXcgPSBvcy5lbnZpcm9uLmdldCgiVEFCTEVfTE9HX0ZJTEUiLCAidGVzdC5sb2ciKS5zdHJpcCgpIG9yICJ0ZXN0LmxvZyINCiAgICAgICAgICAgICAgICBsb2dfZmlsZSA9IG9zLnBhdGguYWJzcGF0aChsb2dfZmlsZV9yYXcpDQoNCiAgICAgICAgICAgICAgICBsb2dnaW5nLmJhc2ljQ29uZmlnKA0KICAgICAgICAgICAgICAgICAgICBsZXZlbD1sb2dfbGV2ZWwsDQogICAgICAgICAgICAgICAgICAgIGZvcm1hdD0iJShhc2N0aW1lKXMgWyUobGV2ZWxuYW1lKXNdICUobmFtZSlzOiAlKG1lc3NhZ2UpcyIsDQogICAgICAgICAgICAgICAgICAgIGhhbmRsZXJzPVsNCiAgICAgICAgICAgICAgICAgICAgICAgIGxvZ2dpbmcuRmlsZUhhbmRsZXIobG9nX2ZpbGUsIGVuY29kaW5nPSJ1dGYtOCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgbG9nZ2luZy5TdHJlYW1IYW5kbGVyKHN5cy5zdGRlcnIpLA0KICAgICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICBMT0dHRVIuaW5mbygiTG9nZ2luZyBpbml0aWFsaXplZCAtPiBmaWxlPSVzIGxldmVsPSVzIiwgbG9nX2ZpbGUsIGxvZ2dpbmcuZ2V0TGV2ZWxOYW1lKGxvZ19sZXZlbCkpDQogICAgICAgICAgICAgICAgX2xvZ19lbnZpcm9ubWVudF92YXJpYWJsZXMoKQ0KICAgICAgICAgICAgc3lzLnN0ZG91dC5yZWNvbmZpZ3VyZShlbmNvZGluZz0idXRmLTgiKSAgIyB0eXBlOiBpZ25vcmVbYXR0ci1kZWZpbmVkXQ0KICAgICAgICBleGNlcHQgRXhjZXB0aW9uOg0KICAgICAgICAgICAgcGFzcw0KICAgICAgICANCiAgICAgICAgcGF5bG9hZCA9IF9sb2FkX3BheWxvYWQoKQ0KICAgICAgICB0YWJsZV9zcGVjID0gYnVpbGRfdGVtcGVyYXR1cmVfdGFibGVfd2l0aF9sb2FkX3N0YXR1cyhwYXlsb2FkKQ0KICAgICAgICByZXN1bHQgPSB7InRhYmxlcyI6IFt0YWJsZV9zcGVjXX0NCiAgICAgICAgcHJpbnQoanNvbi5kdW1wcyhyZXN1bHQsIGVuc3VyZV9hc2NpaT1GYWxzZSkpDQogICAgICAgIHJldHVybiAwDQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGM6DQogICAgICAgIHByaW50KGYiZXJyb3I6IHtleGN9IiwgZmlsZT1zeXMuc3RkZXJyKQ0KICAgICAgICByZXR1cm4gMQ0KDQoNCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6DQogICAgc3lzLmV4aXQobWFpbigpKQ0K", "scriptName": "table.py", "remark": "PCM性能测试实验" }, @@ -401,11 +178,13 @@ }, "globalParameters": { "parameters": { - "work_order_no": "W2001150.001-01:10", - "process_no": "W2001150.001-01:10", - "part_no": "P67-13-103", - "executor": "朱吉生", - "current_date": "2025-12-03" + "work_order_no": "COT8888", + "process_no": "TEST-8888", + "part_no": "TEST-PART-8888", + "executor": "测试人员", + "current_date": "2026-03-10", + "config_type": "600泵", + "process_name": "泵空跑合" } } } \ No newline at end of file diff --git a/diagnose_experiment_list.py b/diagnose_experiment_list.py new file mode 100644 index 0000000..c34c8f9 --- /dev/null +++ b/diagnose_experiment_list.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +诊断实验列表显示问题 +""" +import sqlite3 +from pathlib import Path +from logger import get_logger + +logger = get_logger() + + +def diagnose(): + """诊断实验列表问题""" + print("\n" + "="*80) + print("诊断实验列表显示问题") + print("="*80 + "\n") + + try: + db_path = Path(__file__).parent / "experiments.db" + + if not db_path.exists(): + print(f"❌ 数据库文件不存在: {db_path}") + return + + print(f"✅ 数据库文件存在: {db_path}\n") + + db = sqlite3.connect(str(db_path)) + cur = db.cursor() + + # 1. 检查表结构 + print("1. 检查 experiments 表结构") + print("-" * 80) + cur.execute("PRAGMA table_info(experiments)") + columns = cur.fetchall() + + print(f"表字段数量: {len(columns)}") + print(f"{'序号':<5} {'字段名':<25} {'类型':<15} {'非空':<5} {'默认值'}") + print("-" * 80) + for col in columns: + cid, name, type_, notnull, default, pk = col + print(f"{cid:<5} {name:<25} {type_:<15} {notnull:<5} {str(default)}") + + # 检查关键字段 + column_names = [col[1] for col in columns] + has_save_status = 'save_status' in column_names + has_save_error = 'save_error' in column_names + + print(f"\n✅ save_status 字段: {'存在' if has_save_status else '❌ 缺失'}") + print(f"✅ save_error 字段: {'存在' if has_save_error else '❌ 缺失'}") + + # 2. 检查实验记录 + print("\n2. 检查实验记录") + print("-" * 80) + cur.execute("SELECT COUNT(*) FROM experiments") + count = cur.fetchone()[0] + print(f"实验记录总数: {count}") + + if count > 0: + # 显示最近的记录 + print("\n最近5条实验记录:") + print("-" * 80) + + if has_save_status and has_save_error: + cur.execute(""" + SELECT id, work_order_no, start_ts, end_ts, save_status, save_error + FROM experiments + ORDER BY id DESC + LIMIT 5 + """) + else: + cur.execute(""" + SELECT id, work_order_no, start_ts, end_ts + FROM experiments + ORDER BY id DESC + LIMIT 5 + """) + + rows = cur.fetchall() + for row in rows: + if has_save_status and has_save_error: + eid, wo, st, et, save_status, save_error = row + print(f"ID: {eid}, 工单: {wo or 'N/A'}, 开始: {st or 'N/A'}, 结束: {et or 'N/A'}") + print(f" 保存状态: {save_status or 'NULL'}, 错误: {save_error or 'NULL'}") + else: + eid, wo, st, et = row + print(f"ID: {eid}, 工单: {wo or 'N/A'}, 开始: {st or 'N/A'}, 结束: {et or 'N/A'}") + + # 3. 测试查询语句 + print("\n3. 测试UI查询语句") + print("-" * 80) + + try: + if has_save_status and has_save_error: + test_sql = """ + SELECT id, start_ts, end_ts, work_order_no, process_name, part_no, + executor, remark, sqlserver_status, is_paused, is_terminated, + save_status, save_error + FROM experiments + ORDER BY id DESC + LIMIT 1 + """ + else: + test_sql = """ + SELECT id, start_ts, end_ts, work_order_no, process_name, part_no, + executor, remark, sqlserver_status, is_paused, is_terminated + FROM experiments + ORDER BY id DESC + LIMIT 1 + """ + + cur.execute(test_sql) + result = cur.fetchone() + + if result: + print("✅ 查询成功") + print(f"返回字段数: {len(result)}") + else: + print("⚠️ 查询成功但无数据") + except Exception as e: + print(f"❌ 查询失败: {e}") + + db.close() + + print("\n" + "="*80) + print("诊断完成") + print("="*80) + + # 给出建议 + if not has_save_status or not has_save_error: + print("\n⚠️ 建议:运行 python add_save_status_columns.py 添加缺失的字段") + + except Exception as e: + print(f"\n❌ 诊断过程出错: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + diagnose() diff --git a/experiment_monitor.py b/experiment_monitor.py index 1d05847..a16f77e 100644 --- a/experiment_monitor.py +++ b/experiment_monitor.py @@ -563,6 +563,7 @@ class ExperimentStateMonitor: """实验开始事件处理""" try: self._experiment_started = True + # 记录本地时间,执行脚本时会自动转换为UTC self._start_time_recorded = datetime.datetime.now().isoformat(timespec='seconds') logger.info( @@ -590,6 +591,7 @@ class ExperimentStateMonitor: return self._experiment_ended = True + # 记录本地时间,执行脚本时会自动转换为UTC self._end_time_recorded = datetime.datetime.now().isoformat(timespec='seconds') logger.info( @@ -786,55 +788,99 @@ class ExperimentStateMonitor: ) def _execute_and_save_script_data(self) -> None: - """执行动态脚本并保存返回数据到数据库""" - try: - from pathlib import Path - import json - - logger.info(f"[脚本执行] 开始为实验{self.experiment_id}执行动态脚本") - - # 获取实验配置 - db_path = Path(__file__).parent / "experiments.db" - db = sqlite3.connect(str(db_path)) - cur = db.cursor() - - cur.execute( - "SELECT config_json, work_order_no FROM experiments WHERE id=?", - (self.experiment_id,) - ) - result = cur.fetchone() - - if not result: - logger.warning(f"[脚本执行] 实验{self.experiment_id}未找到配置") + """执行动态脚本并保存返回数据到数据库(带重试机制)""" + max_retries = 3 + retry_delay = 2 # 秒 + + for attempt in range(max_retries): + try: + from pathlib import Path + import json + + logger.info( + f"[脚本执行] 开始为实验{self.experiment_id}执行动态脚本 " + f"(尝试 {attempt + 1}/{max_retries})" + ) + + # 获取实验配置和时间范围 + db_path = Path(__file__).parent / "experiments.db" + db = sqlite3.connect(str(db_path)) + cur = db.cursor() + + cur.execute( + "SELECT config_json, work_order_no, start_ts, end_ts FROM experiments WHERE id=?", + (self.experiment_id,) + ) + result = cur.fetchone() + + if not result: + logger.warning(f"[脚本执行] 实验{self.experiment_id}未找到配置") + db.close() + self._mark_save_failed("未找到实验配置") + return + + config_json, work_order_no, start_ts, end_ts = result db.close() - return - - config_json, work_order_no = result - db.close() - - # 解析配置 - from config_model import AppConfig - from tempfile import NamedTemporaryFile - - with NamedTemporaryFile('w', delete=False, suffix='.json', encoding='utf-8') as tf: - tf.write(config_json) - snap_path = Path(tf.name) - - config = AppConfig.load(snap_path) - - # 执行脚本 - from report_generator import _execute_experiment_script - script_data = _execute_experiment_script(config) - - if script_data: + + # 解析配置 + from config_model import AppConfig + from tempfile import NamedTemporaryFile + import os + + logger.info(f"[脚本执行] 实验{self.experiment_id}正在解析配置...") + with NamedTemporaryFile('w', delete=False, suffix='.json', encoding='utf-8') as tf: + tf.write(config_json) + snap_path = Path(tf.name) + + config = AppConfig.load(snap_path) + logger.info(f"[脚本执行] 实验{self.experiment_id}配置解析成功") + + # 设置环境变量(时间范围) + prev_start = os.environ.get("EXPERIMENT_START") + prev_end = os.environ.get("EXPERIMENT_END") + + try: + if start_ts: + os.environ["EXPERIMENT_START"] = start_ts + logger.info(f"[脚本执行] 设置 EXPERIMENT_START={start_ts}") + if end_ts: + os.environ["EXPERIMENT_END"] = end_ts + logger.info(f"[脚本执行] 设置 EXPERIMENT_END={end_ts}") + + # 执行脚本 + logger.info(f"[脚本执行] 实验{self.experiment_id}正在执行动态脚本...") + from report_generator import _execute_experiment_script + script_data = _execute_experiment_script(config) + finally: + # 恢复环境变量 + if prev_start is None: + os.environ.pop("EXPERIMENT_START", None) + else: + os.environ["EXPERIMENT_START"] = prev_start + if prev_end is None: + os.environ.pop("EXPERIMENT_END", None) + else: + os.environ["EXPERIMENT_END"] = prev_end + + if not script_data: + logger.warning(f"[脚本执行] 实验{self.experiment_id}脚本未返回数据") + self._mark_save_failed("脚本未返回数据") + return + + logger.info( + f"[脚本执行] 实验{self.experiment_id}脚本执行成功," + f"返回数据字段: {list(script_data.keys())}" + ) + # 保存脚本数据到 SQLite 数据库 + logger.info(f"[脚本执行] 实验{self.experiment_id}正在保存数据到 SQLite...") script_data_json = json.dumps(script_data, ensure_ascii=False) db = sqlite3.connect(str(db_path)) cur = db.cursor() cur.execute( - "UPDATE experiments SET script_data=? WHERE id=?", + "UPDATE experiments SET script_data=?, save_status='success' WHERE id=?", (script_data_json, self.experiment_id) ) db.commit() @@ -847,12 +893,57 @@ class ExperimentStateMonitor: # 写入 SQL Server(如果配置了) self._write_to_sqlserver(script_data, work_order_no, config) - else: - logger.warning(f"[脚本执行] 实验{self.experiment_id}脚本未返回数据") + + # 成功保存,退出重试循环 + logger.info(f"[脚本执行] ✅ 实验{self.experiment_id}数据保存完成") + return + + except Exception as e: + is_last_attempt = (attempt == max_retries - 1) + + if is_last_attempt: + # 最后一次尝试失败,记录错误并标记失败状态 + error_msg = f"执行脚本失败(已重试{max_retries}次): {str(e)}" + logger.error( + f"[脚本执行] ❌ 实验{self.experiment_id}{error_msg}", + exc_info=True + ) + self._mark_save_failed(error_msg) + else: + # 非最后一次尝试,等待后重试 + logger.warning( + f"[脚本执行] ⚠️ 实验{self.experiment_id}执行失败(第{attempt + 1}次尝试)," + f"{retry_delay}秒后重试: {e}" + ) + time.sleep(retry_delay) + + def _mark_save_failed(self, error_message: str) -> None: + """标记数据保存失败状态""" + try: + from pathlib import Path + db_path = Path(__file__).parent / "experiments.db" + + db = sqlite3.connect(str(db_path)) + cur = db.cursor() + + # 更新保存状态为失败,并记录错误信息 + cur.execute( + """UPDATE experiments + SET save_status='failed', + save_error=? + WHERE id=?""", + (error_message, self.experiment_id) + ) + db.commit() + db.close() + + logger.error( + f"[保存失败] ❌ 实验{self.experiment_id}数据保存失败已标记: {error_message}" + ) except Exception as e: logger.error( - f"[脚本执行] 实验{self.experiment_id}执行脚本失败: {e}", + f"[保存失败] 标记失败状态时出错: {e}", exc_info=True ) diff --git a/main.py b/main.py index 92eaa11..1ea737b 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import os import traceback import sys +from single_instance import SingleInstance # 在导入任何Qt模块之前设置Qt WebEngine环境变量 # 这样可以避免PySide6 6.8+版本在Windows上的DirectComposition和GPU崩溃问题 @@ -19,18 +20,26 @@ os.environ["QT_OPENGL"] = "software" os.environ["QTWEBENGINE_DISABLE_SANDBOX"] = "1" if __name__ == "__main__": - try: - import ui_main - ui_main.run_app() - except Exception as e: - print("=" * 60) - print("程序崩溃!错误类型:", type(e).__name__) - print("错误信息:", str(e)) - print("=" * 60) - traceback.print_exc() - print("=" * 60) - input("按Enter键退出...") - except SystemExit as e: - if e.code != 0: - print(f"程序异常退出,退出码: {e.code}") + with SingleInstance() as can_run: + if not can_run: + print("=" * 60) + print("程序已在运行中,不允许重复启动!") + print("=" * 60) input("按Enter键退出...") + sys.exit(1) + + try: + import ui_main + ui_main.run_app() + except Exception as e: + print("=" * 60) + print("程序崩溃!错误类型:", type(e).__name__) + print("错误信息:", str(e)) + print("=" * 60) + traceback.print_exc() + print("=" * 60) + input("按Enter键退出...") + except SystemExit as e: + if e.code != 0: + print(f"程序异常退出,退出码: {e.code}") + input("按Enter键退出...") diff --git a/single_instance.py b/single_instance.py new file mode 100644 index 0000000..61b7c04 --- /dev/null +++ b/single_instance.py @@ -0,0 +1,44 @@ +import os +import sys +import tempfile +from pathlib import Path + +class SingleInstance: + def __init__(self, app_name="PCM_Report"): + self.lockfile = Path(tempfile.gettempdir()) / f"{app_name}.lock" + self.fp = None + + def __enter__(self): + try: + if self.lockfile.exists(): + # 尝试读取PID,检查进程是否还在运行 + try: + pid = int(self.lockfile.read_text().strip()) + # 检查进程是否存在 + if sys.platform == "win32": + import ctypes + kernel32 = ctypes.windll.kernel32 + PROCESS_QUERY_INFORMATION = 0x0400 + handle = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid) + if handle: + kernel32.CloseHandle(handle) + return False # 进程存在,不允许启动 + else: + os.kill(pid, 0) # Unix系统检查 + return False + except (ValueError, ProcessLookupError, OSError): + # 进程不存在,删除旧锁文件 + self.lockfile.unlink(missing_ok=True) + + # 创建锁文件 + self.lockfile.write_text(str(os.getpid())) + return True + except Exception: + return False + + def __exit__(self, *args): + try: + if self.lockfile.exists(): + self.lockfile.unlink() + except Exception: + pass diff --git a/test_auto_save_fix.py b/test_auto_save_fix.py new file mode 100644 index 0000000..c24f181 --- /dev/null +++ b/test_auto_save_fix.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +测试实验结束后自动保存数据的修复方案 +""" +import sqlite3 +from pathlib import Path +from logger import get_logger + +logger = get_logger() + + +def test_save_status_columns(): + """测试保存状态字段是否存在""" + try: + db_path = Path(__file__).parent / "experiments.db" + + if not db_path.exists(): + logger.error(f"数据库文件不存在: {db_path}") + return False + + db = sqlite3.connect(str(db_path)) + cur = db.cursor() + + # 检查字段 + cur.execute("PRAGMA table_info(experiments)") + columns = {row[1]: row[2] for row in cur.fetchall()} + + logger.info(f"experiments 表字段: {list(columns.keys())}") + + # 验证新字段 + has_save_status = 'save_status' in columns + has_save_error = 'save_error' in columns + + logger.info(f"save_status 字段存在: {has_save_status}") + logger.info(f"save_error 字段存在: {has_save_error}") + + if not has_save_status or not has_save_error: + logger.warning("⚠️ 缺少保存状态字段,请运行 add_save_status_columns.py") + db.close() + return False + + # 查询有结束时间但没有保存状态的实验 + cur.execute(""" + SELECT id, work_order_no, end_ts, save_status, save_error + FROM experiments + WHERE end_ts IS NOT NULL + ORDER BY id DESC + LIMIT 10 + """) + + rows = cur.fetchall() + logger.info(f"\n最近10个已结束的实验:") + logger.info(f"{'ID':<5} {'工单号':<15} {'结束时间':<20} {'保存状态':<10} {'错误信息'}") + logger.info("-" * 80) + + for eid, work_order, end_ts, save_status, save_error in rows: + status_display = save_status or "未记录" + error_display = (save_error[:30] + "...") if save_error and len(save_error) > 30 else (save_error or "") + logger.info(f"{eid:<5} {work_order or 'N/A':<15} {end_ts or 'N/A':<20} {status_display:<10} {error_display}") + + db.close() + + logger.info("\n✅ 保存状态字段测试通过") + return True + + except Exception as e: + logger.error(f"❌ 测试失败: {e}", exc_info=True) + return False + + +def test_monitor_retry_logic(): + """测试监控器的重试逻辑""" + logger.info("\n" + "="*80) + logger.info("测试监控器重试逻辑") + logger.info("="*80) + + try: + from experiment_monitor import ExperimentStateMonitor + + # 检查方法是否存在 + has_mark_failed = hasattr(ExperimentStateMonitor, '_mark_save_failed') + has_execute_save = hasattr(ExperimentStateMonitor, '_execute_and_save_script_data') + + logger.info(f"_mark_save_failed 方法存在: {has_mark_failed}") + logger.info(f"_execute_and_save_script_data 方法存在: {has_execute_save}") + + if has_mark_failed and has_execute_save: + logger.info("✅ 监控器重试逻辑已实现") + return True + else: + logger.error("❌ 监控器缺少必要的方法") + return False + + except Exception as e: + logger.error(f"❌ 测试失败: {e}", exc_info=True) + return False + + +def main(): + """运行所有测试""" + print("\n" + "="*80) + print("实验结束自动保存修复方案测试") + print("="*80 + "\n") + + results = [] + + # 测试1: 数据库字段 + print("测试1: 检查数据库保存状态字段...") + results.append(("数据库字段", test_save_status_columns())) + + # 测试2: 监控器重试逻辑 + print("\n测试2: 检查监控器重试逻辑...") + results.append(("监控器重试", test_monitor_retry_logic())) + + # 汇总结果 + print("\n" + "="*80) + print("测试结果汇总") + print("="*80) + + for name, passed in results: + status = "✅ 通过" if passed else "❌ 失败" + print(f"{name:<20} {status}") + + all_passed = all(passed for _, passed in results) + + print("\n" + "="*80) + if all_passed: + print("✅ 所有测试通过!修复方案已正确实施") + else: + print("❌ 部分测试失败,请检查上述错误信息") + print("="*80 + "\n") + + return all_passed + + +if __name__ == "__main__": + import sys + success = main() + sys.exit(0 if success else 1) diff --git a/test_cot8888.py b/test_cot8888.py new file mode 100644 index 0000000..cc50b62 --- /dev/null +++ b/test_cot8888.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +测试 COT8888 测试工单号功能 +""" +from work_order_query import query_work_order +from logger import get_logger + +logger = get_logger() + + +def test_cot8888(): + """测试 COT8888 工单号""" + print("\n" + "="*60) + print("测试 COT8888 测试工单号") + print("="*60 + "\n") + + # 测试不同的大小写 + test_cases = ['COT8888', 'cot8888', 'Cot8888'] + + for work_order_no in test_cases: + print(f"\n测试工单号: {work_order_no}") + print("-" * 40) + + result = query_work_order(work_order_no) + + if result: + print("✅ 查询成功!") + print(f" 工单号: {result.get('work_order_no')}") + print(f" 工序号: {result.get('process_no')}") + print(f" 工序名称: {result.get('process_name')}") + print(f" 零件号: {result.get('part_no')}") + print(f" 执行人: {result.get('executor')}") + else: + print("❌ 查询失败") + + print("\n" + "="*60) + print("测试完成") + print("="*60 + "\n") + + +if __name__ == "__main__": + test_cot8888() diff --git a/ui_main.py b/ui_main.py index 0f08c81..ee346cf 100644 --- a/ui_main.py +++ b/ui_main.py @@ -1128,6 +1128,9 @@ class MainWindow(QMainWindow): self.logger = get_logger() self.logger.info("MainWindow initialized") + # 自动检查并更新数据库结构 + self._auto_migrate_database() + # 启动界面引用 self._splash = None @@ -1445,6 +1448,9 @@ class MainWindow(QMainWindow): self._alarm_timer.timeout.connect(self._on_alarm_tick) self._alarm_polling = False self._alarm_timer.start() + + # 启动 PCM_Viewer(延迟执行,确保主窗口完全初始化) + QTimer.singleShot(1000, self._auto_start_pcm_viewer) def _update_debug_mode_ui(self) -> None: """根据debug模式更新UI显示""" @@ -3982,6 +3988,45 @@ class MainWindow(QMainWindow): db.close() self._reload_experiments() + def _auto_migrate_database(self) -> None: + """程序启动时自动检查并更新数据库结构""" + try: + db_path = APP_DIR / "experiments.db" + + if not db_path.exists(): + self.logger.info("[数据库迁移] 数据库文件不存在,跳过迁移") + return + + db = sqlite3.connect(str(db_path)) + cur = db.cursor() + + # 检查是否需要添加 save_status 和 save_error 字段 + cur.execute("PRAGMA table_info(experiments)") + columns = [row[1] for row in cur.fetchall()] + + needs_migration = False + + if 'save_status' not in columns: + self.logger.info("[数据库迁移] 添加 save_status 字段...") + cur.execute("ALTER TABLE experiments ADD COLUMN save_status TEXT DEFAULT NULL") + needs_migration = True + + if 'save_error' not in columns: + self.logger.info("[数据库迁移] 添加 save_error 字段...") + cur.execute("ALTER TABLE experiments ADD COLUMN save_error TEXT DEFAULT NULL") + needs_migration = True + + if needs_migration: + db.commit() + self.logger.info("[数据库迁移] ✅ 数据库结构更新完成") + else: + self.logger.info("[数据库迁移] 数据库结构已是最新,无需更新") + + db.close() + + except Exception as e: + self.logger.error(f"[数据库迁移] ❌ 自动迁移失败: {e}", exc_info=True) + def _reload_experiments(self) -> None: # 保护:如果被后台线程误调用,立即切回主线程执行 try: @@ -4024,9 +4069,18 @@ class MainWindow(QMainWindow): try: db = sqlite3.connect(str(APP_DIR / "experiments.db")) cur = db.cursor() + + # 检查是否有新字段(向后兼容) + cur.execute("PRAGMA table_info(experiments)") + columns = [row[1] for row in cur.fetchall()] + has_save_fields = 'save_status' in columns and 'save_error' in columns - # 根据首页筛选条件拼接查询 - base_sql = "SELECT id, start_ts, end_ts, work_order_no, process_name, part_no, executor, remark, sqlserver_status, is_paused, is_terminated FROM experiments" + # 根据首页筛选条件拼接查询(如果有新字段则包含) + if has_save_fields: + base_sql = "SELECT id, start_ts, end_ts, work_order_no, process_name, part_no, executor, remark, sqlserver_status, is_paused, is_terminated, save_status, save_error FROM experiments" + else: + base_sql = "SELECT id, start_ts, end_ts, work_order_no, process_name, part_no, executor, remark, sqlserver_status, is_paused, is_terminated FROM experiments" + conds: list[str] = [] params: list[str] = [] @@ -4050,14 +4104,31 @@ class MainWindow(QMainWindow): rows = [] self.exp_history_table.setRowCount(len(rows)) - for r, (eid, st, et, work_order_no, process_name, part_no, executor, remark, sqlserver_status, is_paused, is_terminated) in enumerate(rows): + for r, row_data in enumerate(rows): + # 兼容新旧数据库结构 + if has_save_fields: + eid, st, et, work_order_no, process_name, part_no, executor, remark, sqlserver_status, is_paused, is_terminated, save_status, save_error = row_data + else: + eid, st, et, work_order_no, process_name, part_no, executor, remark, sqlserver_status, is_paused, is_terminated = row_data + save_status = None + save_error = None + self.exp_history_table.setItem(r, 0, QTableWidgetItem(str(st or ""))) self.exp_history_table.setItem(r, 1, QTableWidgetItem(str(et or ""))) self.exp_history_table.setItem(r, 2, QTableWidgetItem(str(work_order_no or ""))) self.exp_history_table.setItem(r, 3, QTableWidgetItem(str(process_name or ""))) self.exp_history_table.setItem(r, 4, QTableWidgetItem(str(part_no or ""))) self.exp_history_table.setItem(r, 5, QTableWidgetItem(str(executor or ""))) - self.exp_history_table.setItem(r, 6, QTableWidgetItem(str(remark or ""))) + + # 备注列:如果保存失败,添加警告标记 + remark_text = str(remark or "") + if save_status == 'failed' and et: + remark_text = f"⚠️ 数据保存失败 | {remark_text}" if remark_text else "⚠️ 数据保存失败" + remark_item = QTableWidgetItem(remark_text) + if save_status == 'failed': + remark_item.setForeground(Qt.red) + remark_item.setToolTip(f"保存失败原因: {save_error or '未知错误'}") + self.exp_history_table.setItem(r, 6, remark_item) # 数据库状态列 db_status_item = QTableWidgetItem(str(sqlserver_status or "")) @@ -4157,8 +4228,19 @@ class MainWindow(QMainWindow): # 保存数据按钮(仅在实验已结束时显示) if et: # 有结束时间 - btn_save_data = QPushButton("保存数据") - btn_save_data.setStyleSheet("QPushButton { background-color: #1976d2; color: white; }") + # 根据保存状态设置按钮文本和样式(仅在有新字段时才判断状态) + if has_save_fields and save_status == 'failed': + btn_save_data = QPushButton("重试保存") + btn_save_data.setStyleSheet("QPushButton { background-color: #f44336; color: white; font-weight: bold; }") + btn_save_data.setToolTip(f"上次保存失败: {save_error or '未知错误'}\n点击重试") + elif has_save_fields and save_status == 'success': + btn_save_data = QPushButton("已保存") + btn_save_data.setStyleSheet("QPushButton { background-color: #4caf50; color: white; }") + btn_save_data.setToolTip("数据已保存,点击可重新保存") + else: + btn_save_data = QPushButton("保存数据") + btn_save_data.setStyleSheet("QPushButton { background-color: #1976d2; color: white; }") + btn_save_data.clicked.connect(lambda _=False, id=eid: self._execute_script_for_experiment(id)) w_save_data = QWidget(); les = QHBoxLayout(); les.setContentsMargins(0,0,0,0); les.addWidget(btn_save_data); les.addStretch(1); w_save_data.setLayout(les) else: @@ -5494,30 +5576,103 @@ class MainWindow(QMainWindow): QMessageBox.critical(self, "错误", f"实验台断电时发生异常: {str(e)}") def _open_dashboard_viewer(self): - """打开 PCM_Viewer 全屏展示""" - # 选择布局文件 - file_path, _ = QFileDialog.getOpenFileName( - self, - "选择看板布局文件", - "", - "JSON文件 (*.json)" - ) - - if not file_path: - return - - # 检查文件是否存在 - from pathlib import Path - if not Path(file_path).exists(): - QMessageBox.warning(self, "警告", "选择的文件不存在!") - return - - # 启动 PCM_Viewer 子进程(隐藏模式,通过UDP通信) + """打开 PCM_Viewer 全屏展示(仅发送UDP命令)""" try: - self._start_pcm_viewer(file_path) + # 第一步:让用户选择配置类型 + # 传递上次选择的配置类型名称作为默认值 + default_category_name = self._current_config_category.name if self._current_config_category else None + self.logger.info(f"打开数据展示,默认配置类型: {default_category_name}") + + # 使用自定义标题和消息的配置类型选择对话框 + from config_type_selector import ConfigTypeSelectorDialog + category = ConfigTypeSelectorDialog.select_config_type( + parent=self, + title="数据展示 - 选择展示类型", + message="请选择要展示的类型:", + default_category_name=default_category_name + ) + + if category is None: + # 用户取消了选择 + self.logger.info("用户取消了数据展示类型选择") + self.statusBar().showMessage("已取消数据展示", 2000) + return + + self.logger.info(f"用户为数据展示选择展示类型: {category.name}") + + # 第二步:构建dashboard.json的完整路径 + # 路径格式: C:\PPRO\PCM_Report\configs\600泵\dashboard.json + dashboard_path = category.path / "dashboard.json" + + # 检查文件是否存在 + if not dashboard_path.exists(): + QMessageBox.warning(self, "警告", f"展示类型 {category.name} 的dashboard.json文件不存在!\n路径: {dashboard_path}") + self.logger.warning(f"dashboard.json不存在: {dashboard_path}") + return + + # 第三步:发送UDP命令 + file_path = str(dashboard_path.resolve()) # 转换为绝对路径字符串 + self._send_udp_command({ + 'action': 'show_and_fullscreen', + 'path': file_path + }) + self.logger.info(f"已发送显示命令: {file_path}") + self.statusBar().showMessage(f"✓ 已发送看板显示命令: {category.name}", 3000) + except Exception as e: - QMessageBox.critical(self, "错误", f"启动看板失败: {str(e)}") - self.logger.error(f"启动看板失败: {e}") + QMessageBox.critical(self, "错误", f"发送看板命令失败: {str(e)}") + self.logger.error(f"发送看板命令失败: {e}") + + def _auto_start_pcm_viewer(self): + """软件启动时自动启动 PCM_Viewer 可执行文件(不发送UDP命令)""" + import subprocess + import sys + import os + + try: + # 获取当前可执行文件所在目录 + if getattr(sys, 'frozen', False): + # 打包后的环境 + app_dir = os.path.dirname(sys.executable) + else: + # 开发环境 + app_dir = os.path.dirname(os.path.abspath(__file__)) + + # 尝试多个可能的路径 + possible_paths = [ + os.path.join(app_dir, "PCM_Viewer.exe"), # 同级目录 + os.path.join(app_dir, "_internal", "PCM_Viewer.exe"), # _internal 目录 + r"C:\PPro\PCM_Viewer\dist\PCM_Viewer.exe" # 开发环境备用路径 + ] + + viewer_path = None + for path in possible_paths: + if os.path.exists(path): + viewer_path = path + break + + if not viewer_path: + self.logger.warning(f"未找到 PCM_Viewer.exe,尝试的路径: {possible_paths}") + return + + # 检查 PCM_Viewer 是否已在运行 + viewer_running = self._check_viewer_running() + + if not viewer_running: + # 启动 PCM_Viewer(不带参数,正常启动) + self.logger.info("自动启动 PCM_Viewer...") + subprocess.Popen( + [viewer_path], + shell=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + creationflags=subprocess.CREATE_NO_WINDOW + ) + self.logger.info("PCM_Viewer 已启动") + else: + self.logger.info("PCM_Viewer 已在运行,跳过启动") + except Exception as e: + self.logger.error(f"自动启动 PCM_Viewer 失败: {e}") def _start_pcm_viewer(self, layout_path: str, udp_port: int = 9876): """启动 PCM_Viewer 并通过 UDP 发送显示命令 @@ -5531,12 +5686,36 @@ class MainWindow(QMainWindow): import subprocess import time import os + import sys - viewer_path = r"F:\PyPro\PCM_Viewer\dist\PCM_Viewer.exe" + # 获取当前可执行文件所在目录 + if getattr(sys, 'frozen', False): + # 打包后的环境 + app_dir = os.path.dirname(sys.executable) + else: + # 开发环境 + app_dir = os.path.dirname(os.path.abspath(__file__)) + + # 尝试多个可能的路径 + possible_paths = [ + os.path.join(app_dir, "PCM_Viewer.exe"), # 同级目录 + os.path.join(app_dir, "_internal", "PCM_Viewer.exe"), # _internal 目录 + r"C:\PPro\PCM_Viewer\dist\PCM_Viewer.exe" # 开发环境备用路径 + ] + + viewer_path = None + for path in possible_paths: + if os.path.exists(path): + viewer_path = path + break + + if not viewer_path: + self.logger.warning(f"未找到 PCM_Viewer.exe,尝试的路径: {possible_paths}") + return # 检查 PCM_Viewer 是否已在运行(通过UDP探测) viewer_running = self._check_viewer_running(udp_port) - + print("viewer_running:", viewer_running) if not viewer_running: # 启动 PCM_Viewer(隐藏模式) self.logger.info("启动 PCM_Viewer...") @@ -5559,23 +5738,25 @@ class MainWindow(QMainWindow): self.logger.info(f"已发送显示命令: {layout_path}") def _check_viewer_running(self, port: int = 9876) -> bool: - """检查 PCM_Viewer 是否已在运行 - + """检查 PCM_Viewer 是否已在运行(通过进程名检测) + Args: - port: UDP 端口 - + port: 保留参数,兼容调用方签名 + Returns: bool: 是否运行中 """ - import socket + import subprocess try: - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(0.5) - # 发送探测命令 - sock.sendto(b'ping', ('127.0.0.1', port)) - sock.close() - return True - except: + result = subprocess.run( + ['tasklist', '/fi', 'imagename eq PCM_Viewer.exe', '/fo', 'csv', '/nh'], + capture_output=True, + text=True, + creationflags=subprocess.CREATE_NO_WINDOW + ) + return 'PCM_Viewer.exe' in result.stdout + except Exception as e: + self.logger.warning(f"进程检测失败,默认视为未运行: {e}") return False def _send_udp_command(self, command: dict, port: int = 9876): @@ -5897,10 +6078,11 @@ class MainWindow(QMainWindow): try: db = sqlite3.connect(str(APP_DIR / "experiments.db")) cur = db.cursor() - # 设置 end_ts 为当前时间(如果还没有),is_terminated = 0(保持正常状态) + # 设置 end_ts 为当前本地时间(如果还没有),is_terminated = 0(保持正常状态) + end_time = datetime.datetime.now().isoformat(timespec="seconds") cur.execute( - "UPDATE experiments SET end_ts = CASE WHEN end_ts IS NULL THEN datetime('now') ELSE end_ts END, is_terminated = 0 WHERE id = ?", - (exp_id,) + "UPDATE experiments SET end_ts = CASE WHEN end_ts IS NULL THEN ? ELSE end_ts END, is_terminated = 0 WHERE id = ?", + (end_time, exp_id) ) db.commit() db.close() diff --git a/work_order_query.py b/work_order_query.py index 05febc4..42694f1 100644 --- a/work_order_query.py +++ b/work_order_query.py @@ -97,6 +97,17 @@ def query_work_order(work_order_no: str) -> Optional[Dict[str, str]]: logger.warning("工单号为空") return None + # 特殊测试工单号:COT8888 - 自动返回假数据用于测试 + if work_order_no.upper() == 'COT8888': + logger.info(f"[测试模式] 检测到测试工单号 {work_order_no},返回假数据") + return { + 'work_order_no': work_order_no, + 'process_no': 'TEST-8888', + 'process_name': WORK_ORDER_DB_CONFIG.get('target_process_name', '泵空跑合'), + 'part_no': 'TEST-PART-8888', + 'executor': '测试人员' + } + # Debug模式:返回假数据 if WORK_ORDER_DB_CONFIG.get('debug_mode', False): logger.info(f"[DEBUG模式] 返回工单号 {work_order_no} 的假数据")