private void WriteMetricsData(BinaryWriter writer, IEnumerable <IReadOnlyMetric> metricData) { Stream writerStream = writer.BaseStream; long currentTimeInMinutes = 0; if (this.version >= 5) { var currentTimeInTicks = DateTime.UtcNow.Ticks; currentTimeInMinutes = (currentTimeInTicks - (currentTimeInTicks % SerializationUtils.OneMinuteInterval)) / SerializationUtils.OneMinuteInterval; SerializationUtils.WriteUInt64AsBase128(writer, (ulong)currentTimeInMinutes); } var metricsCountPosition = writer.BaseStream.Position; writer.Write((uint)0); uint metricsCount = 0; foreach (var data in metricData) { ++metricsCount; var metadata = data.MetricMetadata; this.WriteMetricMetadataIndex(writer, metadata); // In versions 0-2 Monitoring Account and Metric Namespace was part of the Metric data // From version 3 Monitoring Account is removed and Metric Namespace became a part of Metric Metadata if (this.version < 3) { SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.MonitoringAccount)); SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.MetricNamespace)); } // In version 0 we had EventId, which was always passed as empty string if (this.version == 0) { SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(string.Empty)); } if (this.version >= 5) { var timeTicks = data.TimeUtc.Ticks; long timeInMinutes = (timeTicks - (timeTicks % SerializationUtils.OneMinuteInterval)) / SerializationUtils.OneMinuteInterval; SerializationUtils.WriteInt64AsBase128(writer, currentTimeInMinutes - timeInMinutes); } else { SerializationUtils.WriteUInt64AsBase128(writer, (ulong)data.TimeUtc.Ticks); } for (byte j = 0; j < data.MetricMetadata.DimensionsCount; ++j) { SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.GetDimensionValue(j))); } var samplingTypes = data.SamplingTypes; bool useDouble = false; bool storeDoubleAsLong = false; if ((samplingTypes & SamplingTypes.DoubleValueType) != 0) { useDouble = true; if (data.SumUnion.CanRepresentDoubleAsLong() && ((samplingTypes & SamplingTypes.Min) == 0 || (data.MinUnion.CanRepresentDoubleAsLong() && data.MaxUnion.CanRepresentDoubleAsLong()))) { samplingTypes = samplingTypes | SamplingTypes.DoubleValueStoredAsLongType; storeDoubleAsLong = true; } } SerializationUtils.WriteUInt32AsBase128(writer, (uint)samplingTypes); if ((data.SamplingTypes & SamplingTypes.Min) != 0) { this.WriteMetricValue(data.MinUnion, useDouble, storeDoubleAsLong, writer); } if ((data.SamplingTypes & SamplingTypes.Max) != 0) { this.WriteMetricValue(data.MaxUnion, useDouble, storeDoubleAsLong, writer); } if ((data.SamplingTypes & SamplingTypes.Sum) != 0) { this.WriteMetricValue(data.SumUnion, useDouble, storeDoubleAsLong, writer); } if ((data.SamplingTypes & SamplingTypes.Count) != 0) { SerializationUtils.WriteUInt32AsBase128(writer, data.Count); } if ((data.SamplingTypes & SamplingTypes.SumOfSquareDiffFromMean) != 0) { writer.Write(data.SumOfSquareDiffFromMean); } if ((data.SamplingTypes & SamplingTypes.Histogram) != 0) { if (data.Histogram == null) { var message = string.Format( CultureInfo.InvariantCulture, "Invalid input data. Declared sampling type contains Histogram, but Histogram data is null. Metric:({0},{1},{2}).", data.MonitoringAccount, data.MetricNamespace, data.MetricMetadata.MetricName); throw new MetricSerializationException(message, null); } SerializationUtils.WriteHistogramDataHistogram(writer, data.Histogram.SamplesCount, data.Histogram.Samples, this.version > 3); } if ((data.SamplingTypes & SamplingTypes.HyperLogLogSketch) != 0) { if (data.HyperLogLogSketchesStream == null && data.HyperLogLogSketches == null) { var message = string.Format( CultureInfo.InvariantCulture, "Invalid input data. Declared sampling type contains sketches, but sketches data is null. Metric:({0},{1},{2}).", data.MonitoringAccount, data.MetricNamespace, data.MetricMetadata.MetricName); throw new MetricSerializationException(message, null); } if (data.HyperLogLogSketchesStream != null) { if (this.tempBuffer == null) { this.tempBuffer = new byte[TempBufferSize]; } writer.Write((int)data.HyperLogLogSketchesStream.Length); var sketchStreamStartPosition = data.HyperLogLogSketchesStream.Position; SerializationUtils.ReadFromStream(data.HyperLogLogSketchesStream, writer.BaseStream, (int)data.HyperLogLogSketchesStream.Length, this.tempBuffer); data.HyperLogLogSketchesStream.Position = sketchStreamStartPosition; } else { if (this.hllSerializationVersion == 0) { SerializationUtils.WriteHyperLogLogSketches(writer, data.HyperLogLogSketches.HyperLogLogSketchesCount, data.HyperLogLogSketches.HyperLogLogSketches); } else { SerializationUtils.WriteHyperLogLogSketchesV2(writer, data.HyperLogLogSketches.HyperLogLogSketchesCount, data.HyperLogLogSketches.HyperLogLogSketches); } } } if (this.version >= 6) { if ((data.SamplingTypes & samplingTypes & SamplingTypes.TDigest) != 0) { SerializationUtils.WriteUInt32AsBase128(writer, FrontEndMetricDeserializer <IMetricMetadata> .TDigestPrefixValue); long pos = writerStream.Position; // placeholder for length encoded as 4 bytes writer.Write((ushort)0); writer.Write((ushort)0); data.TDigest.Serialize(writer); long tdigestSerializedLength = writerStream.Position - pos - 4; if (tdigestSerializedLength > ushort.MaxValue) { throw new ArgumentException("TDigest too big"); } writerStream.Position = pos; SerializationUtils.WriteUInt32InBase128AsFixed4Bytes(writer, (ushort)tdigestSerializedLength); writerStream.Position += tdigestSerializedLength; } SerializationUtils.WriteUInt32AsBase128(writer, 0); } this.currentMetricDataBlockSize = writer.BaseStream.Position; } var currentPosition = writer.BaseStream.Position; writer.BaseStream.Position = metricsCountPosition; // Versions before 2 used variable number of bytes to write number of serialized metrics data. // From version 2 passing IEnumerable<IReadOnlyMetric> is supported, thus number of metrics data // is unknown beforehand and we cannot use variable number anymore. Thus we use fixed 4 bytes // uint. To keep compatibility with previous versions, while still have ability to serialize // variable amount of data, we write uint number in variable manner but with fixed number of bytes. if (this.version >= 2) { writer.Write(metricsCount); } else { SerializationUtils.WriteUInt32InBase128AsFixed4Bytes(writer, metricsCount); } writer.BaseStream.Position = currentPosition; }