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

Signal disk buffering #913

Merged
merged 213 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from 203 commits
Commits
Show all changes
213 commits
Select commit Hold shift + click to select a range
2056cbf
Creating empty module for disk exporters
LikeTheSalad Jun 2, 2023
347a2b9
Updated new exporters-storage module gradle file and package name
LikeTheSalad Jun 2, 2023
ba9dd8a
Renaming exporters-storage module to disk-buffer
LikeTheSalad Jun 6, 2023
0619a73
Adding AttributesJsonConverter
LikeTheSalad Jun 6, 2023
67be26c
Adding span json types
LikeTheSalad Jun 6, 2023
d1214af
Adding log json types
LikeTheSalad Jun 6, 2023
fb08847
Adding metric json types
LikeTheSalad Jun 6, 2023
1c24365
Validating custom MetricDataJsonConverter
LikeTheSalad Jun 6, 2023
248dffb
Changing package name
LikeTheSalad Jun 6, 2023
51bfb99
Moving serialization into internal package
LikeTheSalad Jun 6, 2023
fbef8fb
Updating module name for disk buffer
LikeTheSalad Jun 6, 2023
67e35e5
Adding span mappers
LikeTheSalad Jun 6, 2023
7697b69
Clean up
LikeTheSalad Jun 6, 2023
42f1f49
Adding metrics mapping
LikeTheSalad Jun 6, 2023
b5874dc
Adding log mapping
LikeTheSalad Jun 6, 2023
69dbcd0
Making classes final
LikeTheSalad Jun 6, 2023
103ab05
Created SignalSerializer
LikeTheSalad Jun 6, 2023
635a8e0
Verifying log serialization
LikeTheSalad Jun 6, 2023
9ec3b73
Renaming base serializer test methods
LikeTheSalad Jun 6, 2023
0d10862
Clean up
LikeTheSalad Jun 6, 2023
875e120
Moving mapping to serialization
LikeTheSalad Jun 6, 2023
cce475c
Clean up imports
LikeTheSalad Jun 6, 2023
36297c6
Reoarganizing json dtos
LikeTheSalad Jun 6, 2023
f92ea01
Reorganizing json custom converters
LikeTheSalad Jun 6, 2023
c1a5935
Using autovalue on metric point data implementation
LikeTheSalad Jun 6, 2023
dbd58d1
Abstracting common metric data point builder methods
LikeTheSalad Jun 6, 2023
62e4598
Validating metrics serialization
LikeTheSalad Jun 6, 2023
10a4a7c
Validating spans serialization
LikeTheSalad Jun 6, 2023
d84d329
Renaming Serializer to JsonSerializer
LikeTheSalad Jun 6, 2023
300479f
Revert "Renaming Serializer to JsonSerializer"
LikeTheSalad Jun 6, 2023
699f458
Clean up
LikeTheSalad Jun 7, 2023
97aa0df
Adding traceState when serializing span links
LikeTheSalad Jun 7, 2023
1e62698
Avoiding to serialize link traceState when it's empty
LikeTheSalad Jun 7, 2023
89e4376
Adding traceState serialization support for spans
LikeTheSalad Jun 7, 2023
5b708d6
Manually registering generated json configurations
LikeTheSalad Jun 7, 2023
114da0f
Moving JsonSerializer to serializers
LikeTheSalad Jun 7, 2023
c56b27c
Renaming module from disk-buffer to disk-buffering
LikeTheSalad Jun 7, 2023
2a96ce6
Renaming disk buffering package
LikeTheSalad Jun 7, 2023
500ecb3
Renaming constant fields
LikeTheSalad Jun 7, 2023
2dbe018
Creating and reusing writable files
LikeTheSalad Jun 7, 2023
33a7c54
Purging old files and creating new one when existing cannot be reused
LikeTheSalad Jun 7, 2023
349cb22
Synchronizing writable file provider method
LikeTheSalad Jun 7, 2023
3652a43
Purging only expired files for reading when creating new one
LikeTheSalad Jun 7, 2023
c2f4626
Updating comments
LikeTheSalad Jun 7, 2023
5133952
Providing readable files only after their min age to read has passed
LikeTheSalad Jun 7, 2023
8ea5ec8
Ensuring the oldest readable file is provided
LikeTheSalad Jun 7, 2023
a875d23
Avoiding to provide expired readable files
LikeTheSalad Jun 7, 2023
03bb26b
Validating writable max file size
LikeTheSalad Jun 7, 2023
9255f73
Making max file size int
LikeTheSalad Jun 7, 2023
9ca6272
Clean up
LikeTheSalad Jun 7, 2023
e3ddd63
Removing old file if space is needed in the folder
LikeTheSalad Jun 7, 2023
975917e
Validating checking existing files after purge is done
LikeTheSalad Jun 7, 2023
c9919d4
Improving getting writable file by listing files once
LikeTheSalad Jun 7, 2023
325661c
Created filderholder implementations
LikeTheSalad Jun 7, 2023
3b3618f
Making fileholder implementations final
LikeTheSalad Jun 7, 2023
03e2e92
Clean up
LikeTheSalad Jun 7, 2023
bbccc77
Reusing writable file from memory
LikeTheSalad Jun 7, 2023
d6a9c81
Not returning current writable file from memory
LikeTheSalad Jun 7, 2023
0e2aece
Starting to implement Writable file methods
LikeTheSalad Jun 7, 2023
dc13e86
Renaming FileProvider to FolderManager
LikeTheSalad Jun 7, 2023
665a464
Deleting reusing mechanism of writable files
LikeTheSalad Jun 8, 2023
9aa9f46
Clean up tests
LikeTheSalad Jun 8, 2023
90e2f8e
Clean up
LikeTheSalad Jun 8, 2023
098d6a1
Appending lines into WritableFile
LikeTheSalad Jun 8, 2023
2ff7afe
Throwing no space exception when no more data can fit into the file b…
LikeTheSalad Jun 8, 2023
e519ff6
Cleaning up
LikeTheSalad Jun 8, 2023
76395f7
Verifying writable file size is updated correctly
LikeTheSalad Jun 8, 2023
18df096
Validating write file appending timeout exception and closed exception
LikeTheSalad Jun 8, 2023
99a60b0
Adding docs to WritableFile append method
LikeTheSalad Jun 8, 2023
6e26e86
Reading files
LikeTheSalad Jun 8, 2023
d1c1ba0
Reading lines and updating the original file
LikeTheSalad Jun 8, 2023
82d4fec
Validating no more lines to read use case
LikeTheSalad Jun 8, 2023
94d523d
Removing temporary file after finishing reading
LikeTheSalad Jun 8, 2023
780afbb
Validating exception thrown when reading timeout expired
LikeTheSalad Jun 8, 2023
60f9b8d
Updated reader's doc
LikeTheSalad Jun 8, 2023
b1c29d8
Clean up
LikeTheSalad Jun 8, 2023
498564d
Validating when readablefile is closed
LikeTheSalad Jun 8, 2023
dd09e5f
Closing readable expired file if needed when purging expired files
LikeTheSalad Jun 8, 2023
3f28b15
Closing readable oldest file is space is needed to create writable file
LikeTheSalad Jun 8, 2023
cadfc0e
Making classes final
LikeTheSalad Jun 8, 2023
1c22ce4
Created Storage class and validating read method
LikeTheSalad Jun 8, 2023
b05e946
Validating storage writing
LikeTheSalad Jun 8, 2023
d3e4942
Validating Storage closing
LikeTheSalad Jun 8, 2023
2344dc8
Validating Storage files reusing
LikeTheSalad Jun 8, 2023
9ab4f23
Handling Configuration with autovalue
LikeTheSalad Jun 8, 2023
8382fa8
Setting configuration defaults
LikeTheSalad Jun 8, 2023
5ab5e25
Renaming Configuration to StorageConfiguration
LikeTheSalad Jun 8, 2023
ab97bae
Created AbstractDiskExporter and each signal's implementation
LikeTheSalad Jun 9, 2023
b7aa2ab
Validating writing in AbstractDiskExporter
LikeTheSalad Jun 9, 2023
cc864ca
Adding verification for signal disk exporters
LikeTheSalad Jun 9, 2023
4285c1f
Moving StorageConfiguration out of internal
LikeTheSalad Jun 9, 2023
7ea45d3
Moving AbstractDiskExporter to internal
LikeTheSalad Jun 9, 2023
ee54d77
Adding docs to StorageConfiguration
LikeTheSalad Jun 9, 2023
1150463
Adding docs to signal disk exporters
LikeTheSalad Jun 9, 2023
039fea5
Created README
LikeTheSalad Jun 9, 2023
67816d0
Deleting ReadableFile after it gets empty
LikeTheSalad Jun 12, 2023
b9b0873
Updating docs
LikeTheSalad Jun 12, 2023
e5759c8
Using FileOutputStream directly in WritableFile
LikeTheSalad Jun 12, 2023
fc2b8c4
Cleaning up the readable file in case it's empty
LikeTheSalad Jun 12, 2023
994ea5f
Improving tests
LikeTheSalad Jun 12, 2023
b296f9a
Updated StorageConfiguration defaults
LikeTheSalad Jun 12, 2023
98b385d
Validating closing a writable file if it has expired and a readable f…
LikeTheSalad Jun 12, 2023
91ec9f0
Adding logs to AbstractDiskExporter
LikeTheSalad Jun 12, 2023
4c73be7
Validating configuration parameters
LikeTheSalad Jun 12, 2023
f18ddb7
Removing java.nio.file.Files usage to make it Android-friendly
LikeTheSalad Jun 12, 2023
4a6f403
Updating readme links
LikeTheSalad Jun 12, 2023
d22da48
Applying spotlessApply
LikeTheSalad Jun 12, 2023
3b42ced
Updating README
LikeTheSalad Jun 12, 2023
52ce4ec
Removing unnecessary runtime dependencies
LikeTheSalad Jun 12, 2023
9ab9aa9
Making TemporaryFileProvider configurable
LikeTheSalad Jun 13, 2023
962d136
Updating README to include a code example
LikeTheSalad Jun 13, 2023
702bdeb
Removing json params not present in the protos
LikeTheSalad Jun 13, 2023
de3b9ec
Adding classes javadoc headers
LikeTheSalad Jun 13, 2023
aefd46e
Update disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buf…
LikeTheSalad Jun 19, 2023
549ea97
Adding component owners
LikeTheSalad Jun 19, 2023
24ba7f3
Merge branch 'main' into disk-buffer
LikeTheSalad Jun 19, 2023
aa48d91
Using Logging Level FINER as replacement for INFO given that INFO is …
LikeTheSalad Jun 19, 2023
c488aa4
Cleaning up logs dependency
LikeTheSalad Jun 19, 2023
3194422
Adding OTel java proto dependency
LikeTheSalad Jun 19, 2023
159ffac
Mapping attributes to and from proto classes
LikeTheSalad Jun 19, 2023
a188b9e
Updating test naming for attributes mapping
LikeTheSalad Jun 19, 2023
c33b4d4
Mapping resources
LikeTheSalad Jun 20, 2023
40716c0
Mapping logs
LikeTheSalad Jun 20, 2023
17b6267
Creating MetricDataMapper for protos
LikeTheSalad Jun 21, 2023
d8b4757
Adding validations for metric mapping
LikeTheSalad Jun 21, 2023
b915182
Adding validations for metric mapping
LikeTheSalad Jun 21, 2023
dc31408
Clean up
LikeTheSalad Jun 22, 2023
914d4e5
Mapping proto SpanData
LikeTheSalad Jun 22, 2023
7d02197
Replacing JSON with DTO words
LikeTheSalad Jun 22, 2023
525fd52
Creating proto data mappers
LikeTheSalad Jun 26, 2023
702513f
Adding proto logs validations
LikeTheSalad Jun 26, 2023
02bc6b4
Adding proto metrics and spans validations
LikeTheSalad Jun 26, 2023
3947cea
Extracting file inputstream read process
LikeTheSalad Jun 26, 2023
ba8f245
Using line reader factory as default for ReadableFile
LikeTheSalad Jun 26, 2023
2b629c2
Adding javadoc
LikeTheSalad Jun 26, 2023
a93c1d9
Improving ReadableFile tests
LikeTheSalad Jun 26, 2023
6dd53fd
Cleaning up json related code
LikeTheSalad Jun 27, 2023
00827c9
Serializing and deserializing using delimited protos
LikeTheSalad Jun 27, 2023
c243d7c
Formatting
LikeTheSalad Jun 27, 2023
fb6f4a9
Making WritableFile to not know about its data delimiters
LikeTheSalad Jun 27, 2023
09b97a3
Updating serializer tests
LikeTheSalad Jun 27, 2023
f69d684
Adding integration tests
LikeTheSalad Jun 27, 2023
0b105d1
Extracting file transfer logic from ReadableFile into FileTransferUtil
LikeTheSalad Jun 27, 2023
315dd3a
Adding benchmark for the process of transferring data from a temporar…
LikeTheSalad Jun 27, 2023
35bab1f
Replacing inheritance with delegation for DiskExporter usage
LikeTheSalad Jun 28, 2023
d6837c5
Returning success on exporters flush
LikeTheSalad Jun 28, 2023
d3be201
Improving storage read return logic
LikeTheSalad Jun 28, 2023
2afb324
Adding information to the README about parallel read/write
LikeTheSalad Jun 28, 2023
66dba3a
Removing custom exceptions in favor of using result codes internally
LikeTheSalad Jun 28, 2023
0e4d6bd
Passing TimeProvider to internal classes in their constructors
LikeTheSalad Jun 28, 2023
4505347
Update disk-buffering/build.gradle.kts
LikeTheSalad Jun 28, 2023
e32252f
Update disk-buffering/build.gradle.kts
LikeTheSalad Jun 28, 2023
b04c758
Adding exception to disk exporters constructor signature
LikeTheSalad Jun 28, 2023
c48c70d
Formatting
LikeTheSalad Jun 28, 2023
76c1fbb
Using static create function to instantiate the disk signal exporters
LikeTheSalad Jun 28, 2023
9d30667
Updating the README file to add more info about the high level process
LikeTheSalad Jun 28, 2023
abedf2b
Created the CONTRIBUTING.md file
LikeTheSalad Jun 28, 2023
884d133
Formatting
LikeTheSalad Jun 28, 2023
0f2803c
Adding license headers
LikeTheSalad Jun 28, 2023
215e5de
Merge remote-tracking branch 'origin/disk-buffer' into disk-buffer
LikeTheSalad Jun 28, 2023
eb5d795
Updating CONTRIBUTING
LikeTheSalad Jun 28, 2023
e123539
Removing mapstruct for metrics
LikeTheSalad Jun 29, 2023
3d64adb
Cleaning up custom metric datapoints
LikeTheSalad Jun 29, 2023
79c4d1a
Cleaning up custom metric point data types
LikeTheSalad Jun 29, 2023
2a8bb8f
Cleaning up custom metric ExponentialHistogramPointData
LikeTheSalad Jun 29, 2023
8e76fd0
Cleaning up custom metric HistogramPointData
LikeTheSalad Jun 29, 2023
7bfbff7
Cleaning up custom metric pointdata utils
LikeTheSalad Jun 29, 2023
d968210
Cleaning up custom metric histogram data
LikeTheSalad Jun 29, 2023
232d42c
Cleaning up custom metric gauge data
LikeTheSalad Jun 29, 2023
505f134
Cleaning up custom metric histogram data
LikeTheSalad Jun 29, 2023
18a6e52
Cleaning up custom metric sum data
LikeTheSalad Jun 29, 2023
d133723
Cleaning up custom metric data types
LikeTheSalad Jun 29, 2023
38eba1d
Cleaning up custom metric impl
LikeTheSalad Jun 29, 2023
68591f3
Removing mapstruct from SpanDataMapper
LikeTheSalad Jun 29, 2023
5198591
Removing mapstruct from LogRecordDataMapper
LikeTheSalad Jun 29, 2023
815dbd6
Removing mapstruct from LogRecordDataMapper
LikeTheSalad Jun 29, 2023
9bea0a4
Removing custom span event data
LikeTheSalad Jun 29, 2023
f143f8f
Changes from Gregor
LikeTheSalad Jun 29, 2023
70889c8
Update disk-buffering/CONTRIBUTING.md
LikeTheSalad Jun 29, 2023
9468083
Merge remote-tracking branch 'origin/disk-buffer' into disk-buffer
LikeTheSalad Jun 29, 2023
fa8dc9f
Removing unused AtomicBoolean in DiskExporter
LikeTheSalad Jun 29, 2023
0d478e7
Removing unused enum values from ReadableResult
LikeTheSalad Jun 29, 2023
fda405e
Removing unused enum values from WritableResult
LikeTheSalad Jun 29, 2023
2479c9a
Updated README
LikeTheSalad Jun 29, 2023
c7eb673
Removing mapstruct from AttributesMapper
LikeTheSalad Jun 29, 2023
5b0b34a
Removing mapstruct from ResourceMapper
LikeTheSalad Jun 29, 2023
2b05d96
Removing mapstruct from the dependencies
LikeTheSalad Jun 29, 2023
522aba2
Updated CONTRIBUTING.md formatting
LikeTheSalad Jun 29, 2023
e897285
Updated IntegrationTest formatting
LikeTheSalad Jun 29, 2023
8596be1
Update disk-buffering/README.md
LikeTheSalad Jul 10, 2023
eb13335
Renaming TimeProvider to StorageClock and implementing OTel's Clock
LikeTheSalad Jul 12, 2023
35c6187
Providing milliseconds in StorageClock.now()
LikeTheSalad Jul 12, 2023
e8abb3b
Making StorageClock final
LikeTheSalad Jul 12, 2023
d55a4d5
Replacing singleton access from INSTANCE field to getInstance() method
LikeTheSalad Jul 12, 2023
9f71217
Update disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buf…
LikeTheSalad Jul 12, 2023
e0dbd4c
Making DefaultTemporaryFileProvider final
LikeTheSalad Jul 12, 2023
6cb3804
Merge remote-tracking branch 'origin/disk-buffer' into disk-buffer
LikeTheSalad Jul 12, 2023
1ed6042
Adding DefaultTemporaryFileProvider getInstance after committing PR s…
LikeTheSalad Jul 12, 2023
21a945b
Moving all public classes outside the `exporters` package
LikeTheSalad Jul 12, 2023
101b932
Moving all classes from `storage` directly to `internal`
LikeTheSalad Jul 12, 2023
9eabe76
Moving all classes from `storage` directly to `internal`
LikeTheSalad Jul 12, 2023
e727b9f
Fixing formatting
LikeTheSalad Jul 12, 2023
d40d829
Fixing md links
LikeTheSalad Jul 12, 2023
b733c9a
Removing usage of buffered input stream in ReadableFile
LikeTheSalad Jul 13, 2023
8acf8b2
Adding animalsniffer check for Android level 24 api support check
LikeTheSalad Jul 13, 2023
80b266a
Removing lazy init for serializers
LikeTheSalad Jul 13, 2023
7236f41
Renaming singleton getter
LikeTheSalad Jul 13, 2023
9d365d7
Replacing exception type when serializing
LikeTheSalad Jul 13, 2023
a8f668d
Removing unnecessary param for FileOutputStream constructor
LikeTheSalad Jul 13, 2023
ba57501
Removing unused Constants class
LikeTheSalad Jul 13, 2023
c712d25
Replacing "line" by "item" wording in Storage.java
LikeTheSalad Jul 13, 2023
60ac5c0
Adding docs to ReadableFile
LikeTheSalad Jul 13, 2023
ad26fb2
Adding docs to ReadableFile.copyFile
LikeTheSalad Jul 13, 2023
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
3 changes: 3 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ components:
consistent-sampling:
- oertl
- PeterF778
disk-buffering:
- LikeTheSalad
- zeitlinger
samplers:
- iNikem
- trask
Expand Down
56 changes: 56 additions & 0 deletions disk-buffering/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Contributor Guide

Each one of the three exporters provided by this
tool ([LogRecordDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordDiskExporter.java), [MetricDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/MetricDiskExporter.java)
and [SpanDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/SpanDiskExporter.java))
is responsible of performing 2 actions, `write` and `read/delegate`, the `write` one happens
automatically as a set of signals are provided from the processor, while the `read/delegate` one has
to be triggered manually by the consumer of this library as explained in the [README](README.md).

## Writing overview

![Writing flow](assets/writing-flow.png)

* The writing process happens automatically within its `export(Collection<SignalData> signals)`
method, which is called by the configured signal processor.
* When a set of signals is received, these are delegated over to
the [DiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/DiskExporter.java)
class which then serializes them using an implementation
of [SignalSerializer](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SignalSerializer.java)
and then the serialized data is appended into a File using an instance of
the [Storage](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java)
class.
* The data is written into a file directly, without the use of a buffer, to make sure no data gets
lost in case the application ends unexpectedly.
* Each disk exporter stores its signals in its own folder, which is expected to contain files
that belong to that type of signal only.
* Each file may contain more than a batch of signals if the configuration parameters allow enough
limit size for it.
* If the configured folder size for the signals has been reached and a new file is needed to be
created to keep storing new data, the oldest available file will be removed to make space for the
new one.
* The [Storage](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java),
[FolderManager](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java)
and [WritableFile](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java)
files contain more information on the details of the writing process into a file.

## Reading overview

![Reading flow](assets/reading-flow.png)

* The reading process has to be triggered manually by the library consumer as explained in
the [README](README.md).
* A single file is read at a time and updated to remove the data gathered from it after it is
successfully exported, until it's emptied. Each file previously created during the
writing process has a timestamp in milliseconds, which is used to determine what file to start
reading from, which will be the oldest one available.
* If the oldest file available is stale, which is determined based on the configuration provided at
the time of creating the disk exporter, then it will be ignored, and the next oldest (and
unexpired) one will be used instead.
* All the stale and empty files will be removed as a new file is created.
* The [Storage](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java),
[FolderManager](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java)
and [ReadableFile](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java)
files contain more information on the details of the file reading process.
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved
* Note that the reader delegates the data to the exporter exactly in the way it has received the
data - it does not try to batch data (but this could be an optimization in the future).
113 changes: 113 additions & 0 deletions disk-buffering/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Disk buffering
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved

This module provides signal exporter wrappers that intercept and store signals in files which can be
sent later on demand. A high level description of how it works is that there are two separate
processes in place, one for writing data in disk, and one for reading/exporting the previously
stored data.

* Each exporter stores the received data automatically in disk right after it's received from its
processor.
* The reading of the data back from disk and exporting process has to be done manually. At
the moment there's no automatic mechanism to do so. There's more information on it can be
achieved, under [Reading data](#reading-data).

> For a more detailed information on how the whole process works, take a look at
> the [CONTRIBUTING](CONTRIBUTING.md) file.

## Configuration

The configurable parameters are provided **per exporter**, the available ones are:

* Max file size, defaults to 1MB.
* Max folder size, defaults to 10MB. All files are stored in a single folder per-signal, therefore
if all 3 types of signals are stored, the total amount of space from disk to be taken by default
would be of 30MB.
* Max age for file writing, defaults to 30 seconds.
* Min age for file reading, defaults to 33 seconds. It must be greater that the max age for file
writing.
* Max age for file reading, defaults to 18 hours. After that time passes, the file will be
considered stale and will be removed when new files are created. No more data will be read from a
file past this time.
* An instance
of [TemporaryFileProvider](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/files/TemporaryFileProvider.java),
defaults to calling `File.createTempFile`. This provider will be used when reading from the disk
in order create a temporary file from which each line (batch of signals) will be read and
sequentially get removed from the original cache file right after the data has been successfully
exported.

## Usage

### Storing data

In order to use it, you need to wrap your own exporter with a new instance of
the ones provided in here:

* For a LogRecordExporter, it must be wrapped within
a [LogRecordDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordDiskExporter.java).
* For a MetricExporter, it must be wrapped within
a [MetricDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/MetricDiskExporter.java).
* For a SpanExporter, it must be wrapped within
a [SpanDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/SpanDiskExporter.java).

Each wrapper will need the following when instantiating them:

* The exporter to be wrapped.
* A File instance of the root directory where all the data is going to be written. The same root dir
can be used for all the wrappers, since each will create their own folder inside it.
* An instance
of [StorageConfiguration](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/StorageConfiguration.java)
with the desired parameters. You can create one with default values by
calling `StorageConfiguration.getDefault()`.

After wrapping your exporters, you must register the wrapper as the exporter you'll use. It will
take care of always storing the data it receives.
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved

#### Set up example for spans

```java
// Creating the SpanExporter of our choice.
SpanExporter mySpanExporter = OtlpGrpcSpanExporter.getDefault();

// Wrapping our exporter with its disk exporter.
SpanDiskExporter diskExporter = SpanDiskExporter.create(mySpanExporter, new File("/my/signals/cache/dir"), StorageConfiguration.getDefault());

// Registering the disk exporter within our OpenTelemetry instance.
SdkTracerProvider myTraceProvider = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(diskExporter))
.build();
OpenTelemetrySdk.builder()
.setTracerProvider(myTraceProvider)
.buildAndRegisterGlobal();

```

### Reading data

Each of the exporter wrappers can read from the disk and send the retrieved data over to their
wrapped exporter by calling this method from them:

```java
try {
if(diskExporter.exportStoredBatch(1, TimeUnit.SECONDS)) {
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved
// A batch was successfully exported and removed from disk. You can call this method for as long as it keeps returning true.
} else {
// Either there was no data in the disk or the wrapped exporter returned CompletableResultCode.ofFailure().
}
} catch (IOException e) {
// Something unexpected happened.
}
```

Both the writing and reading processes can run in parallel and they don't overlap
because each is supposed to happen in different files. We ensure that reader and writer don't
accidentally meet in the same file by using the configurable parameters. These parameters set non-overlapping time frames for each action to be done on a single file at a time. On top of that, there's a mechanism in
place to avoid overlapping on edge cases where the time frames ended but the resources haven't been
released. For that mechanism to work properly, this tool assumes that both the reading and the
writing actions are executed within the same application process.

## Component owners

- [Cesar Munoz](https://github.com/LikeTheSalad), Elastic
- [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana

Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).
Binary file added disk-buffering/assets/reading-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added disk-buffering/assets/writing-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions disk-buffering/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
plugins {
id("otel.java-conventions")
id("otel.publish-conventions")
id("me.champeau.jmh") version "0.7.1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both opentelemetry-java and opentelemetry-java-instrumentation use animalsniffer to avoid using apis that are not available on android, see if it would be useful in this project

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I've just added it to verify API 24 supported code

}

description = "Exporter implementations that store signals on disk"
otelJava.moduleName.set("io.opentelemetry.contrib.exporters.disk")

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

val autovalueVersion = "1.10.1"
dependencies {
api("io.opentelemetry:opentelemetry-sdk")
implementation("io.opentelemetry:opentelemetry-exporter-otlp-common")
implementation("io.opentelemetry.proto:opentelemetry-proto:0.20.0-alpha")
compileOnly("com.google.auto.value:auto-value-annotations:$autovalueVersion")
annotationProcessor("com.google.auto.value:auto-value:$autovalueVersion")
testImplementation("org.mockito:mockito-inline:4.11.0")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
}

jmh {
warmupIterations.set(0)
fork.set(2)
iterations.set(5)
timeOnIteration.set("5s")
timeUnit.set("ms")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.disk.buffering.internal.files.utils;

import io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils.FileTransferUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;

public class FileTransferUtilBenchmark {

@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void fileTransfer(FileTransferState state) throws IOException {
state.fileTransferUtil.transferBytes(state.offset, state.amountOfBytesToTransfer);
}

@State(Scope.Benchmark)
public static class FileTransferState {
public FileTransferUtil fileTransferUtil;
public int offset;
public int amountOfBytesToTransfer;
private File inputFile;
private File outputFile;

@Setup
public void setUp() throws IOException {
outputFile = File.createTempFile("output", ".txt");
inputFile = File.createTempFile("input", ".txt");
int totalDataSize = 1024 * 1024; // 1MB
byte[] data = new byte[totalDataSize];
Files.write(inputFile.toPath(), data, StandardOpenOption.CREATE);
fileTransferUtil = new FileTransferUtil(new FileInputStream(inputFile), outputFile);
offset = 512;
amountOfBytesToTransfer = totalDataSize - offset;
}

@TearDown
public void tearDown() throws IOException {
fileTransferUtil.close();
inputFile.delete();
outputFile.delete();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.disk.buffering;

import io.opentelemetry.contrib.disk.buffering.internal.StorageConfiguration;
import io.opentelemetry.contrib.disk.buffering.internal.exporters.DiskExporter;
import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer;
import io.opentelemetry.contrib.disk.buffering.internal.storage.utils.StorageClock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.LogRecordProcessor;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

/**
* This is a {@link LogRecordExporter} wrapper that takes care of intercepting all the signals sent
* out to be exported, tries to store them in the disk in order to export them later.
*
* <p>In order to use it, you need to wrap your own {@link LogRecordExporter} with a new instance of
* this one, which will be the one you need to register in your {@link LogRecordProcessor}.
*/
public final class LogRecordDiskExporter implements LogRecordExporter, StoredBatchExporter {
private final LogRecordExporter wrapped;
private final DiskExporter<LogRecordData> diskExporter;

/**
* Creates a new instance of {@link LogRecordDiskExporter}.
*
* @param wrapped - The exporter where the data retrieved from the disk will be delegated to.
* @param rootDir - The directory to create this signal's cache dir where all the data will be
* written into.
* @param configuration - How you want to manage the storage process.
* @throws IOException If no dir can be created in rootDir.
*/
public static LogRecordDiskExporter create(
LogRecordExporter wrapped, File rootDir, StorageConfiguration configuration)
throws IOException {
return create(wrapped, rootDir, configuration, StorageClock.getInstance());
}

// This is used for testing purposes.
static LogRecordDiskExporter create(
LogRecordExporter wrapped,
File rootDir,
StorageConfiguration configuration,
StorageClock clock)
throws IOException {
return new LogRecordDiskExporter(wrapped, rootDir, configuration, clock);
}

private LogRecordDiskExporter(
LogRecordExporter wrapped,
File rootDir,
StorageConfiguration configuration,
StorageClock clock)
throws IOException {
this.wrapped = wrapped;
diskExporter =
new DiskExporter<>(
rootDir, configuration, "logs", SignalSerializer.ofLogs(), wrapped::export, clock);
}

@Override
public CompletableResultCode export(Collection<LogRecordData> logs) {
return diskExporter.onExport(logs);
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
try {
diskExporter.onShutDown();
} catch (IOException e) {
return CompletableResultCode.ofFailure();
} finally {
wrapped.shutdown();
}
return CompletableResultCode.ofSuccess();
}

@Override
public boolean exportStoredBatch(long timeout, TimeUnit unit) throws IOException {
return diskExporter.exportStoredBatch(timeout, unit);
}
}
Loading