<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Server Diagnostics</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
table {
border-collapse: collapse;
}
td, th {
border: 1px solid black;
padding: 0.5rem;
}
table.side_headings td, table.side_headings th {
text-align: left;
}
th {
background: lightblue;
font-weight: bold;
}
</style>
</head>
<body onload="requestMetrics()">
<script>
function requestMetrics() {
$.getJSON("/server/metrics").done(json => {
fillMetrics(json);
});
}
function abortServer() {
$.get("/server/abort").done(json => {
alert("Server will be aborted (debug build only)")
});
}
function attribute_id_string(attribute_id) {
switch (attribute_id) {
case 1:
return "NodeId";
case 2:
return "NodeClass";
case 3:
return "BrowseName";
case 4:
return "DisplayName";
case 5:
return "Description";
case 6:
return "WriteMask";
case 7:
return "UserWriteMask";
case 8:
return "IsAbstract";
case 9:
return "Symmetric";
case 10:
return "InverseName";
case 11:
return "ContainsNoLoops";
case 12:
return "EventNotifier";
case 13:
return "Value";
case 14:
return "DataType";
case 15:
return "ValueRank";
case 16:
return "ArrayDimensions";
case 17:
return "AccessLevel";
case 18:
return "UserAccessLevel";
case 19:
return "MinimumSamplingInterval";
case 20:
return "Historizing";
case 21:
return "Executable";
case 22:
return "UserExecutable";
default:
return "Unknown";
}
}
function node_id_str(node_id) {
const identifier = node_id.identifier;
if (identifier["String"]) {
return `ns=${node_id.namespace};s=${identifier["String"].value}`;
} else if (identifier["Numeric"]) {
return `ns=${node_id.namespace};i=${identifier["Numeric"]}`;
} else if (identifier["Guid"]) {
return `ns=${node_id.namespace};g=${identifier["Guid"].value}`;
} else if (identifier["ByteString"]) {
return `ns=${node_id.namespace};b=......`;
} else {
return `Invalid node id`
}
}
function fillMetrics(json) {
let html = `<h1 class='server'>Server</h1>`;
html += dump_server(json["config"], json["server"]);
html += dump_diagnostics(json["diagnostics"]);
html += dump_runtime_components(json["runtime_components"]);
const connections = json["connections"];
html += "<h1 class='connections'>Client connections</h1>";
if (connections.length === 0) {
html += "<p>No connections</p>";
} else {
connections.forEach(connection => {
html += dump_connection(connection);
});
}
$("#metrics").html(html);
}
function msToTime(duration) {
let milliseconds = parseInt("" + (duration % 1000) / 100);
let seconds = Math.floor((duration / 1000) % 60);
let minutes = Math.floor((duration / (1000 * 60)) % 60);
let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds + "." + milliseconds;
}
function dump_server(config, server) {
return `<table class="side_headings">
<tr><th>Server name</th><td>${config["application_name"]}</td></tr>
<tr><th>Server URI</th><td>${config["application_uri"]}</td></tr>
<tr><th>Start Time</th><td>${new Date(server["start_time"]).toString()}</td></tr>
<tr><th>Uptime</th><td>${msToTime(server["uptime_ms"])}</td></tr>
<tr><th>Actions</th><td><button onclick="abortServer()">Terminate Server</button> Debug Builds Only</td></tr>
</table>
`;
}
function dump_connection(connection) {
let html = `<h2>Client "${connection["id"]}"</h2>
<table class="side_headings">
<tr><th>Client address</th><td>${connection["client_address"]}</td></tr>
<tr><th>Transport state</th><td>${connection["transport_state"]}</td></tr>
<tr><th>Session activated</th><td>${connection["session_activated"]}</td></tr>
<tr><th>Session terminated</th><td>${connection["session_terminated"]}</td></tr>function
<tr><th>Session terminated at</th><td>${connection["session_terminated_at"]}</td></tr>
</table>`;
html += `<h2 class='subscriptions'>Subscriptions</h2>`
if (connection.hasOwnProperty("subscriptions")) {
const subscription_metrics = connection["subscriptions"];
html += `<table>
<tr><th>Publish Requests</th><td>${subscription_metrics.publish_request_queue_len}</td></tr>
<tr><th>Publish Responses</th><td>${subscription_metrics.publish_response_queue_len}</td></tr>
<tr><th>Transmission Queue</th><td>${subscription_metrics.transmission_queue_len}</td></tr>
<tr><th>Retransmission Queue</th><td>${subscription_metrics.retransmission_queue_len}</td></tr>
</table>`;
const subscriptions = subscription_metrics["subscriptions"];
if (subscriptions.length === 0) {
html += `<p>No subscriptions</p>`;
} else {
subscriptions.forEach(subscription => {
html += dump_subscription(subscription);
});
}
}
return html;
}
function dump_subscription(subscription) {
let html = `<h3>Subscription ${subscription.subscription_id}</h3>
<table class="side_headings">
<tr><th>Priority</th><td>${subscription.priority}</td></tr>
<tr><th>Publishing enabled</th><td>${subscription.publishing_enabled}</td></tr>
<tr><th>Publishing interval</th><td>${subscription.publishing_interval}ms</td></tr>
<tr><th>Last Timer Expiration</th><td>${subscription.last_timer_expired_time}</td></tr>
<tr><th>Lifetime Counter</th><td>${subscription.lifetime_counter} / ${subscription.max_lifetime_counter}</td></tr>
<tr><th>Keep-Alive Counter</th><td>${subscription.keep_alive_counter} / ${subscription.max_keep_alive_counter}</td></tr>
</table>`;
var monitored_items = subscription.monitored_items;
if (monitored_items.length === 0) {
html += "<p>No monitored items</p>";
} else {
html += dump_monitored_items(monitored_items);
}
return html;
}
function dump_monitored_items(monitored_items) {
let html = `
<h3>Monitored Items</h3>
<table>
<thead>
<tr>
<th>Node Id</th>
<th>Last Value</th>
<th>Mode</th>
<th>Client #</th>
<th>Filter</th>
<th>Sampling interval</th>
<th>Discard Oldest</th>
<th>Queue Size</th>
<th>Timestamps to return</th>
<th>Last Sample Time</th>
</tr>
</thead>
<tbody>`;
Object.keys(monitored_items).forEach(key => {
const item = monitored_items[key];
html += dump_monitored_item(item);
});
html += `
</tbody>
</table>`;
return html;
}
function dump_monitored_item(item) {
let node_id = node_id_str(item.item_to_monitor.node_id);
if (item.item_to_monitor.attribute_id != 13) {
node_id += ` / ${attribute_id_string(item.item_to_monitor.attribute_id)}`;
}
let last_data_value = item.last_data_value;
if (!last_data_value) {
last_data_value = "-";
} else if (typeof last_data_value === "object") {
last_data_value = last_data_value.value;
if ("Boolean" in last_data_value) {
last_data_value = last_data_value.Boolean + " / Boolean";
} else if ("String" in last_data_value) {
last_data_value = (last_data_value.String.value) ? last_data_value.String.value : "NULL";
last_data_value += " / String"
} else if ("Byte" in last_data_value) {
last_data_value = last_data_value.Byte + " / Byte";
} else if ("SByte" in last_data_value) {
last_data_value = last_data_value.SByte + " / SByte";
} else if ("UInt16" in last_data_value) {
last_data_value = last_data_value.UInt16 + " / UInt16";
} else if ("Int16" in last_data_value) {
last_data_value = last_data_value.Int16 + " / Int16";
} else if ("UInt32" in last_data_value) {
last_data_value = last_data_value.UInt32 + " / UInt32";
} else if ("Int32" in last_data_value) {
last_data_value = last_data_value.Int32 + " / Int32";
} else if ("UInt64" in last_data_value) {
last_data_value = last_data_value.UInt64 + " / UInt64";
} else if ("Int64" in last_data_value) {
last_data_value = last_data_value.Int64 + " / Int64";
} else if ("Float" in last_data_value) {
last_data_value = last_data_value.Float + " / Float";
} else if ("Double" in last_data_value) {
last_data_value = last_data_value.Double + " / Double";
} else if ("Guid" in last_data_value) {
last_data_value = last_data_value.Guid + " / Guid";
} else {
last_data_value = JSON.stringify(last_data_value);
}
}
let filter = "";
if (typeof item.filter === "string") {
filter = item.filter;
} else if ("EventFilter" in item.filter) {
filter = "EventFilter";
} else if ("DataChangeFilter" in item.filter) {
filter = "DataChangeFilter";
}
return `
<tr>
<td>${node_id}</td>
<td>${last_data_value}</td>
<td>${item.monitoring_mode}</td>
<td>${item.client_handle}</td>
<td>${filter}</td>
<td>${item.sampling_interval}ms</td>
<td>${item.discard_oldest}</td>
<td>${item.queue_size}</td>
<td>${item.timestamps_to_return}</td>
<td>${item.last_sample_time}</td>
</tr>
`;
}
function dump_runtime_components(runtime_components) {
let html = `<h2 class='runtime_components_summary'>Runtime Summary</h2>
<p>Represents tasks that are running.</p>
`;
html += `<table>`;
runtime_components.forEach(component => {
html += `<tr><td>${component}</td></tr>`;
});
html += `</table>`;
return html;
}
function dump_diagnostics(diagnostics) {
const diagnostics_summary = diagnostics["server_diagnostics_summary"];
return `<h2 class='diagnostic_summary'>Diagnostic Summary</h2>
<table>
<thead>
<tr>
<th colspan="6">Sessions</th>
<th colspan="3">Subscriptions</th>
<th colspan="2">Requests</th>
</tr>
<tr>
<!-- Sesssions -->
<th>Current</th>
<th>Cumulative</th>
<th>Security Rejected</th>
<th>Rejected</th>
<th>Timeouts</th>
<th>Aborts</th>
<!-- Subscriptions -->
<th>Current</th>
<th>Cumulative</th>
<th>Publishing Intervals</th>
<!-- Requests -->
<th>Security Rejected</th>
<th>Rejected</th>
</tr>
</thead>
<tbody>
<tr>
<td>${diagnostics_summary["current_session_count"]}</td>
<td>${diagnostics_summary["cumulated_session_count"]}</td>
<td>${diagnostics_summary["security_rejected_session_count"]}</td>
<td>${diagnostics_summary["rejected_session_count"]}</td>
<td>${diagnostics_summary["session_timeout_count"]}</td>
<td>${diagnostics_summary["session_abort_count"]}</td>
<td>${diagnostics_summary["current_subscription_count"]}</td>
<td>${diagnostics_summary["cumulated_subscription_count"]}</td>
<td>${diagnostics_summary["publishing_interval_count"]}</td>
<td>${diagnostics_summary["security_rejected_requests_count"]}</td>
<td>${diagnostics_summary["rejected_requests_count"]}</td>
</tr>
</tbody>
</table>
`
;
}
</script>
<div><span><a href="#" onclick="requestMetrics()">Reload</a></span></div>
<div id="metrics"><span>Please wait...</span></div>
<div><span><a href="#" onclick="requestMetrics()">Reload</a></span></div>
</body>
</html>