Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fork in persistent storage implementation to otlp #4793

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<!-- this is temporary. will remove in future PR. -->
<Nullable>disable</Nullable>
<DefineConstants>BUILDING_INTERNAL_PERSISTENT_STORAGE;$(DefineConstants)</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand All @@ -30,6 +31,7 @@
<Compile Include="$(RepoRoot)\src\Shared\SemanticConventions.cs" Link="Includes\SemanticConventions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SpanAttributeConstants.cs" Link="Includes\SpanAttributeConstants.cs" />
<Compile Include="$(RepoRoot)\src\Shared\StatusHelper.cs" Link="Includes\StatusHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Shims\NullableAttributes.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// <copyright file="DirectorySizeTracker.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

#nullable enable

namespace OpenTelemetry.PersistentStorage.FileSystem;

/// <summary>
/// Tracks the available storage in a specified directory.
/// </summary>
internal sealed class DirectorySizeTracker
{
private readonly long maxSizeInBytes;
private readonly string path;
private long directoryCurrentSizeInBytes;

public DirectorySizeTracker(long maxSizeInBytes, string path)
{
this.maxSizeInBytes = maxSizeInBytes;
this.path = path;
this.directoryCurrentSizeInBytes = CalculateFolderSize(path);
}

public void FileAdded(long fileSizeInBytes) => Interlocked.Add(ref this.directoryCurrentSizeInBytes, fileSizeInBytes);

public void FileRemoved(long fileSizeInBytes) => Interlocked.Add(ref this.directoryCurrentSizeInBytes, fileSizeInBytes * -1);

/// <summary>
/// Checks if the space is available for new blob.
/// </summary>
/// <remarks>
/// This method is not thread safe and may give false positives/negatives.
/// False positive is ok because the file write will eventually fail.
/// False negative is ok as the file write can be retried if needed.
/// This is done in order to avoid acquiring lock while writing/deleting the blobs.
/// </remarks>
/// <param name="currentSizeInBytes">Size of blob to be written.</param>
/// <returns>True if space is available else false.</returns>
public bool IsSpaceAvailable(out long currentSizeInBytes)
{
currentSizeInBytes = Interlocked.Read(ref this.directoryCurrentSizeInBytes);
return currentSizeInBytes < this.maxSizeInBytes;
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
}

public void RecountCurrentSize()
{
var size = CalculateFolderSize(this.path);
Interlocked.Exchange(ref this.directoryCurrentSizeInBytes, size);
}

internal static long CalculateFolderSize(string path)
{
if (!Directory.Exists(path))
{
return 0;
}

long directorySize = 0;
try
{
foreach (string file in Directory.EnumerateFiles(path))
{
if (File.Exists(file))
{
FileInfo fileInfo = new FileInfo(file);
directorySize += fileInfo.Length;
}
}

foreach (string dir in Directory.GetDirectories(path))
{
directorySize += CalculateFolderSize(dir);
}
}
catch (Exception ex)
{
PersistentStorageEventSource.Log.PersistentStorageException(nameof(PersistentStorageHelper), "Error calculating folder size", ex);
}

return directorySize;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// <copyright file="FileBlob.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

#nullable enable

using System.Diagnostics.CodeAnalysis;
using OpenTelemetry.Internal;
using OpenTelemetry.PersistentStorage.Abstractions;

namespace OpenTelemetry.PersistentStorage.FileSystem;

/// <summary>
/// The <see cref="FileBlob"/> allows to save a blob
/// in file storage.
/// </summary>

#if BUILDING_INTERNAL_PERSISTENT_STORAGE
internal sealed class FileBlob : PersistentBlob
#else
public class FileBlob : PersistentBlob
#endif
{
private readonly DirectorySizeTracker? directorySizeTracker;

/// <summary>
/// Initializes a new instance of the <see cref="FileBlob"/>
/// class.
/// </summary>
/// <param name="fullPath">Absolute file path of the blob.</param>
public FileBlob(string fullPath)
: this(fullPath, null)
{
}

internal FileBlob(string fullPath, DirectorySizeTracker? directorySizeTracker)
{
this.FullPath = fullPath;
this.directorySizeTracker = directorySizeTracker;
}

public string FullPath { get; private set; }

protected override bool OnTryRead([NotNullWhen(true)] out byte[]? buffer)
{
try
{
buffer = File.ReadAllBytes(this.FullPath);
}
catch (Exception ex)
{
PersistentStorageEventSource.Log.CouldNotReadFileBlob(this.FullPath, ex);
buffer = null;
return false;
}

return true;
}

protected override bool OnTryWrite(byte[] buffer, int leasePeriodMilliseconds = 0)
{
Guard.ThrowIfNull(buffer);

string path = this.FullPath + ".tmp";

try
{
PersistentStorageHelper.WriteAllBytes(path, buffer);

if (leasePeriodMilliseconds > 0)
{
var timestamp = DateTime.UtcNow + TimeSpan.FromMilliseconds(leasePeriodMilliseconds);
this.FullPath += $"@{timestamp:yyyy-MM-ddTHHmmss.fffffffZ}.lock";
}

File.Move(path, this.FullPath);
}
catch (Exception ex)
{
PersistentStorageEventSource.Log.CouldNotWriteFileBlob(path, ex);
return false;
}

this.directorySizeTracker?.FileAdded(buffer.LongLength);
return true;
}

protected override bool OnTryLease(int leasePeriodMilliseconds)
{
var path = this.FullPath;
var leaseTimestamp = DateTime.UtcNow + TimeSpan.FromMilliseconds(leasePeriodMilliseconds);
if (path.EndsWith(".lock", StringComparison.OrdinalIgnoreCase))
{
path = path.Substring(0, path.LastIndexOf('@'));
}

path += $"@{leaseTimestamp:yyyy-MM-ddTHHmmss.fffffffZ}.lock";

try
{
File.Move(this.FullPath, path);
}
catch (Exception ex)
{
PersistentStorageEventSource.Log.CouldNotLeaseFileBlob(this.FullPath, ex);
return false;
}

this.FullPath = path;

return true;
}

protected override bool OnTryDelete()
{
try
{
PersistentStorageHelper.RemoveFile(this.FullPath, out var fileSize);
this.directorySizeTracker?.FileRemoved(fileSize);
}
catch (Exception ex)
{
PersistentStorageEventSource.Log.CouldNotDeleteFileBlob(this.FullPath, ex);
return false;
}

return true;
}
}
Loading