/* ========================================================================
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Opc.Ua.PubSub.Encoding;
using Opc.Ua.PubSub.Transcoding;
using JsonDataSetMessageV2 = Opc.Ua.PubSub.Encoding.Json.JsonDataSetMessage;
using JsonEncoderV2 = Opc.Ua.PubSub.Encoding.Json.JsonEncoder;
using JsonNetworkMessageV2 = Opc.Ua.PubSub.Encoding.Json.JsonNetworkMessage;
using UadpDataSetMessageV2 = Opc.Ua.PubSub.Encoding.Uadp.UadpDataSetMessage;
using UadpEncoderV2 = Opc.Ua.PubSub.Encoding.Uadp.UadpEncoder;
using UadpNetworkMessageV2 = Opc.Ua.PubSub.Encoding.Uadp.UadpNetworkMessage;
namespace Opc.Ua.PubSub.Tests.Benchmarks
{
///
/// Micro-benchmarks for the PubSub transcoder covering cross-encoding
/// (UADP↔JSON), the raw-frame identity fast path, and a field
/// projection pipeline. Implements the transcode path over the
///
/// Part 14 §7.2.4 / §7.2.5 mappings.
///
[MemoryDiagnoser]
public class TranscodingBenchmarks
{
private TranscodeContext m_context = null!;
private PubSubTranscoder m_uadpToJson = null!;
private PubSubTranscoder m_jsonToUadp = null!;
private PubSubTranscoder m_identity = null!;
private PubSubTranscoder m_projection = null!;
private TranscodeInput m_uadpInput;
private TranscodeInput m_jsonInput;
private TranscodeInput m_identityInput;
[GlobalSetup]
public async Task SetupAsync()
{
m_context = new TranscodeContext(
BenchmarkContext.NewContext(), BenchmarkTelemetry.Instance);
Dictionary encoders = BuildEncoders();
m_uadpToJson = new PubSubTranscoder(
new TranscodeSpec { TargetEncoding = TranscodeEncoding.Json }, encoders, m_context);
m_jsonToUadp = new PubSubTranscoder(
new TranscodeSpec { TargetEncoding = TranscodeEncoding.Uadp }, encoders, m_context);
m_identity = new PubSubTranscoder(
new TranscodeSpec { TargetEncoding = TranscodeEncoding.Uadp }, encoders, m_context);
m_projection = new PubSubTranscoder(
new TranscodeSpec
{
TargetEncoding = TranscodeEncoding.Json,
Transforms = [new FieldProjectionTransform(["f0", "f1", "f2"])]
},
encoders,
m_context);
UadpNetworkMessageV2 uadp = BuildUadp(10);
ReadOnlyMemory frame = await new UadpEncoderV2()
.EncodeAsync(uadp, m_context.EncodingContext).ConfigureAwait(false);
m_uadpInput = new TranscodeInput(uadp);
m_identityInput = new TranscodeInput(uadp, frame);
m_jsonInput = new TranscodeInput(BuildJson(10));
}
[Benchmark]
public ValueTask UadpToJson()
{
return m_uadpToJson.TranscodeAsync(m_uadpInput);
}
[Benchmark]
public ValueTask JsonToUadp()
{
return m_jsonToUadp.TranscodeAsync(m_jsonInput);
}
[Benchmark(Baseline = true)]
public ValueTask IdentityFastPath()
{
return m_identity.TranscodeAsync(m_identityInput);
}
[Benchmark]
public ValueTask ProjectionToJson()
{
return m_projection.TranscodeAsync(m_uadpInput);
}
private static Dictionary BuildEncoders()
{
var uadp = new UadpEncoderV2();
var json = new JsonEncoderV2();
return new Dictionary(StringComparer.Ordinal)
{
[uadp.TransportProfileUri] = uadp,
[json.TransportProfileUri] = json
};
}
private static UadpNetworkMessageV2 BuildUadp(int fieldCount)
{
return new UadpNetworkMessageV2
{
ContentMask = UadpNetworkMessageContentMask.PublisherId |
UadpNetworkMessageContentMask.PayloadHeader,
PublisherId = PublisherId.FromByte(1),
WriterGroupId = 10,
DataSetMessages =
[
new UadpDataSetMessageV2
{
DataSetWriterId = 100,
MessageType = PubSubDataSetMessageType.KeyFrame,
FieldEncoding = PubSubFieldEncoding.Variant,
Fields = BuildFields(fieldCount)
}
]
};
}
private static JsonNetworkMessageV2 BuildJson(int fieldCount)
{
return new JsonNetworkMessageV2
{
PublisherId = PublisherId.FromByte(1),
WriterGroupId = 10,
DataSetMessages =
[
new JsonDataSetMessageV2
{
DataSetWriterId = 100,
MessageType = PubSubDataSetMessageType.KeyFrame,
Fields = BuildFields(fieldCount)
}
]
};
}
private static DataSetField[] BuildFields(int fieldCount)
{
var fields = new DataSetField[fieldCount];
for (int i = 0; i < fieldCount; i++)
{
fields[i] = new DataSetField
{
Name = "f" + i.ToString(System.Globalization.CultureInfo.InvariantCulture),
Value = new Variant(i)
};
}
return fields;
}
private sealed class BenchmarkTelemetry : TelemetryContextBase
{
public static readonly BenchmarkTelemetry Instance = new();
private BenchmarkTelemetry()
: base(Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance)
{
}
}
}
}