├── .github └── workflows │ ├── codeql-analysis.yml │ ├── dotnet.yml │ └── nuget.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── ManagedCode.TimeSeries.Benchmark ├── Benchmarks │ ├── Bench1.cs │ └── CollectionBenchmark.cs ├── ManagedCode.TimeSeries.Benchmark.csproj └── Program.cs ├── ManagedCode.TimeSeries.Orleans ├── Accumulators │ ├── Converters │ │ ├── DoubleTimeSeriesAccumulatorConverter.cs │ │ ├── FloatTimeSeriesAccumulatorConverter.cs │ │ └── IntTimeSeriesAccumulatorConverter.cs │ └── TimeSeriesAccumulatorsSurrogate.cs ├── ManagedCode.TimeSeries.Orleans.csproj └── Summers │ ├── Converters │ ├── DoubleTimeSeriesSummerConverter.cs │ ├── FloatTimeSeriesSummerConverter.cs │ └── IntTimeSeriesSummerConverter.cs │ └── TimeSeriesSummerSurrogate.cs ├── ManagedCode.TimeSeries.Tests ├── AccumulatorsTests.cs ├── ManagedCode.TimeSeries.Tests.csproj └── SummersTests.cs ├── ManagedCode.TimeSeries.sln ├── ManagedCode.TimeSeries ├── Abstractions │ ├── BaseGroupNumberTimeSeriesSummer.cs │ ├── BaseNumberTimeSeriesSummer.cs │ ├── BaseTimeSeries.cs │ ├── BaseTimeSeriesAccumulator.cs │ ├── BaseTimeSeriesByValueAccumulator.cs │ ├── BaseTimeSeriesSummer.cs │ ├── ISummerItem.cs │ ├── ITimeSeries.cs │ └── Strategy.cs ├── Accumulators │ ├── DoubleTimeSeriesAccumulator.cs │ ├── FloatTimeSeriesAccumulator.cs │ ├── IntTimeSeriesAccumulator.cs │ └── StringTimeSeriesAccumulator.cs ├── Extensions │ └── RoundDateTimeAndTimeSpanExtensions.cs ├── ManagedCode.TimeSeries.csproj ├── MarkupDirection.cs └── Summers │ ├── DoubleGroupTimeSeriesSummer.cs │ ├── DoubleTimeSeriesSummer.cs │ ├── FloatGroupNumberTimeSeriesSummer.cs │ ├── FloatTimeSeriesSummer.cs │ ├── IntGroupNumberTimeSeriesSummer.cs │ └── IntTimeSeriesSummer.cs ├── README.md └── logo.png /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '35 11 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'csharp' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | 15 | build-and-test: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | 20 | - uses: actions/checkout@v2 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v1 23 | with: 24 | dotnet-version: 7.0.x 25 | 26 | # run build and test 27 | - name: Restore dependencies 28 | run: dotnet restore 29 | - name: Build 30 | run: dotnet build --no-restore 31 | - name: Test and сollect Code Coverage 32 | run: dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=lcov -p:CoverletOutput=ManagedCode.Communication.Tests/ 33 | - name: coveralls 34 | uses: coverallsapp/github-action@master 35 | with: 36 | github-token: ${{secrets.GITHUB_TOKEN }} 37 | path-to-lcov: ManagedCode.TimeSeries.Tests/lcov.info -------------------------------------------------------------------------------- /.github/workflows/nuget.yml: -------------------------------------------------------------------------------- 1 | name: nuget 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | jobs: 11 | nuget-pack: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 7.0.x 21 | 22 | - name: Restore dependencies 23 | run: dotnet restore 24 | - name: Build 25 | run: dotnet build --configuration Release 26 | - name: Pack 27 | run: dotnet pack -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg --configuration Release 28 | 29 | - name: publish nuget packages 30 | run: | 31 | shopt -s globstar 32 | for file in **/*.nupkg 33 | do 34 | dotnet nuget push "$file" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json 35 | done -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/artifacts 37 | # .idea/compiler.xml 38 | # .idea/jarRepositories.xml 39 | # .idea/modules.xml 40 | # .idea/*.iml 41 | # .idea/modules 42 | # *.iml 43 | # *.ipr 44 | 45 | # CMake 46 | cmake-build-*/ 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Cursive Clojure plugin 64 | .idea/replstate.xml 65 | 66 | # Crashlytics plugin (for Android Studio and IntelliJ) 67 | com_crashlytics_export_strings.xml 68 | crashlytics.properties 69 | crashlytics-build.properties 70 | fabric.properties 71 | 72 | # Editor-based Rest Client 73 | .idea/httpRequests 74 | 75 | # Android studio 3.1+ serialized cache file 76 | .idea/caches/build_file_checksums.ser 77 | 78 | ### Intellij Patch ### 79 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 80 | 81 | # *.iml 82 | # modules.xml 83 | # .idea/misc.xml 84 | # *.ipr 85 | 86 | # Sonarlint plugin 87 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 88 | .idea/**/sonarlint/ 89 | 90 | # SonarQube Plugin 91 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 92 | .idea/**/sonarIssues.xml 93 | 94 | # Markdown Navigator plugin 95 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 96 | .idea/**/markdown-navigator.xml 97 | .idea/**/markdown-navigator-enh.xml 98 | .idea/**/markdown-navigator/ 99 | 100 | # Cache file creation bug 101 | # See https://youtrack.jetbrains.com/issue/JBR-2257 102 | .idea/$CACHE_FILE$ 103 | 104 | # CodeStream plugin 105 | # https://plugins.jetbrains.com/plugin/12206-codestream 106 | .idea/codestream.xml 107 | 108 | ### Intellij+all ### 109 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 110 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 111 | 112 | # User-specific stuff 113 | 114 | # Generated files 115 | 116 | # Sensitive or high-churn files 117 | 118 | # Gradle 119 | 120 | # Gradle and Maven with auto-import 121 | # When using Gradle or Maven with auto-import, you should exclude module files, 122 | # since they will be recreated, and may cause churn. Uncomment if using 123 | # auto-import. 124 | # .idea/artifacts 125 | # .idea/compiler.xml 126 | # .idea/jarRepositories.xml 127 | # .idea/modules.xml 128 | # .idea/*.iml 129 | # .idea/modules 130 | # *.iml 131 | # *.ipr 132 | 133 | # CMake 134 | 135 | # Mongo Explorer plugin 136 | 137 | # File-based project format 138 | 139 | # IntelliJ 140 | 141 | # mpeltonen/sbt-idea plugin 142 | 143 | # JIRA plugin 144 | 145 | # Cursive Clojure plugin 146 | 147 | # Crashlytics plugin (for Android Studio and IntelliJ) 148 | 149 | # Editor-based Rest Client 150 | 151 | # Android studio 3.1+ serialized cache file 152 | 153 | ### Intellij+all Patch ### 154 | # Ignores the whole .idea folder and all .iml files 155 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 156 | 157 | .idea/ 158 | 159 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 160 | 161 | *.iml 162 | modules.xml 163 | .idea/misc.xml 164 | *.ipr 165 | 166 | # Sonarlint plugin 167 | .idea/sonarlint 168 | 169 | ### Linux ### 170 | *~ 171 | 172 | # temporary files which can be created if a process still has a handle open of a deleted file 173 | .fuse_hidden* 174 | 175 | # KDE directory preferences 176 | .directory 177 | 178 | # Linux trash folder which might appear on any partition or disk 179 | .Trash-* 180 | 181 | # .nfs files are created when an open file is removed but is still being accessed 182 | .nfs* 183 | 184 | ### macOS ### 185 | # General 186 | .DS_Store 187 | .AppleDouble 188 | .LSOverride 189 | 190 | # Icon must end with two \r 191 | Icon 192 | 193 | 194 | # Thumbnails 195 | ._* 196 | 197 | # Files that might appear in the root of a volume 198 | .DocumentRevisions-V100 199 | .fseventsd 200 | .Spotlight-V100 201 | .TemporaryItems 202 | .Trashes 203 | .VolumeIcon.icns 204 | .com.apple.timemachine.donotpresent 205 | 206 | # Directories potentially created on remote AFP share 207 | .AppleDB 208 | .AppleDesktop 209 | Network Trash Folder 210 | Temporary Items 211 | .apdisk 212 | 213 | ### Rider ### 214 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 215 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 216 | 217 | # User-specific stuff 218 | 219 | # Generated files 220 | 221 | # Sensitive or high-churn files 222 | 223 | # Gradle 224 | 225 | # Gradle and Maven with auto-import 226 | # When using Gradle or Maven with auto-import, you should exclude module files, 227 | # since they will be recreated, and may cause churn. Uncomment if using 228 | # auto-import. 229 | # .idea/artifacts 230 | # .idea/compiler.xml 231 | # .idea/jarRepositories.xml 232 | # .idea/modules.xml 233 | # .idea/*.iml 234 | # .idea/modules 235 | # *.iml 236 | # *.ipr 237 | 238 | # CMake 239 | 240 | # Mongo Explorer plugin 241 | 242 | # File-based project format 243 | 244 | # IntelliJ 245 | 246 | # mpeltonen/sbt-idea plugin 247 | 248 | # JIRA plugin 249 | 250 | # Cursive Clojure plugin 251 | 252 | # Crashlytics plugin (for Android Studio and IntelliJ) 253 | 254 | # Editor-based Rest Client 255 | 256 | # Android studio 3.1+ serialized cache file 257 | 258 | ### VisualStudioCode ### 259 | .vscode/* 260 | !.vscode/tasks.json 261 | !.vscode/launch.json 262 | *.code-workspace 263 | 264 | ### VisualStudioCode Patch ### 265 | # Ignore all local history of files 266 | .history 267 | .ionide 268 | 269 | ### Windows ### 270 | # Windows thumbnail cache files 271 | Thumbs.db 272 | Thumbs.db:encryptable 273 | ehthumbs.db 274 | ehthumbs_vista.db 275 | 276 | # Dump file 277 | *.stackdump 278 | 279 | # Folder config file 280 | [Dd]esktop.ini 281 | 282 | # Recycle Bin used on file shares 283 | $RECYCLE.BIN/ 284 | 285 | # Windows Installer files 286 | *.cab 287 | *.msi 288 | *.msix 289 | *.msm 290 | *.msp 291 | 292 | # Windows shortcuts 293 | *.lnk 294 | 295 | ### VisualStudio ### 296 | ## Ignore Visual Studio temporary files, build results, and 297 | ## files generated by popular Visual Studio add-ons. 298 | ## 299 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 300 | 301 | # User-specific files 302 | *.rsuser 303 | *.suo 304 | *.user 305 | *.userosscache 306 | *.sln.docstates 307 | 308 | # User-specific files (MonoDevelop/Xamarin Studio) 309 | *.userprefs 310 | 311 | # Mono auto generated files 312 | mono_crash.* 313 | 314 | # Build results 315 | [Dd]ebug/ 316 | [Dd]ebugPublic/ 317 | [Rr]elease/ 318 | [Rr]eleases/ 319 | x64/ 320 | x86/ 321 | [Aa][Rr][Mm]/ 322 | [Aa][Rr][Mm]64/ 323 | bld/ 324 | [Bb]in/ 325 | [Oo]bj/ 326 | [Ll]og/ 327 | [Ll]ogs/ 328 | 329 | # Visual Studio 2015/2017 cache/options directory 330 | .vs/ 331 | # Uncomment if you have tasks that create the project's static files in wwwroot 332 | #wwwroot/ 333 | 334 | # Visual Studio 2017 auto generated files 335 | Generated\ Files/ 336 | 337 | # MSTest test Results 338 | [Tt]est[Rr]esult*/ 339 | [Bb]uild[Ll]og.* 340 | 341 | # NUnit 342 | *.VisualState.xml 343 | TestResult.xml 344 | nunit-*.xml 345 | 346 | # Build Results of an ATL Project 347 | [Dd]ebugPS/ 348 | [Rr]eleasePS/ 349 | dlldata.c 350 | 351 | # Benchmark Results 352 | BenchmarkDotNet.Artifacts/ 353 | 354 | # .NET Core 355 | project.lock.json 356 | project.fragment.lock.json 357 | artifacts/ 358 | 359 | # StyleCop 360 | StyleCopReport.xml 361 | 362 | # Files built by Visual Studio 363 | *_i.c 364 | *_p.c 365 | *_h.h 366 | *.ilk 367 | *.meta 368 | *.obj 369 | *.iobj 370 | *.pch 371 | *.pdb 372 | *.ipdb 373 | *.pgc 374 | *.pgd 375 | *.rsp 376 | *.sbr 377 | *.tlb 378 | *.tli 379 | *.tlh 380 | *.tmp 381 | *.tmp_proj 382 | *_wpftmp.csproj 383 | *.log 384 | *.vspscc 385 | *.vssscc 386 | .builds 387 | *.pidb 388 | *.svclog 389 | *.scc 390 | 391 | # Chutzpah Test files 392 | _Chutzpah* 393 | 394 | # Visual C++ cache files 395 | ipch/ 396 | *.aps 397 | *.ncb 398 | *.opendb 399 | *.opensdf 400 | *.sdf 401 | *.cachefile 402 | *.VC.db 403 | *.VC.VC.opendb 404 | 405 | # Visual Studio profiler 406 | *.psess 407 | *.vsp 408 | *.vspx 409 | *.sap 410 | 411 | # Visual Studio Trace Files 412 | *.e2e 413 | 414 | # TFS 2012 Local Workspace 415 | $tf/ 416 | 417 | # Guidance Automation Toolkit 418 | *.gpState 419 | 420 | # ReSharper is a .NET coding add-in 421 | _ReSharper*/ 422 | *.[Rr]e[Ss]harper 423 | *.DotSettings.user 424 | 425 | # TeamCity is a build add-in 426 | _TeamCity* 427 | 428 | # DotCover is a Code Coverage Tool 429 | *.dotCover 430 | 431 | # AxoCover is a Code Coverage Tool 432 | .axoCover/* 433 | !.axoCover/settings.json 434 | 435 | # Coverlet is a free, cross platform Code Coverage Tool 436 | coverage*[.json, .xml, .info] 437 | 438 | # Visual Studio code coverage results 439 | *.coverage 440 | *.coveragexml 441 | 442 | # NCrunch 443 | _NCrunch_* 444 | .*crunch*.local.xml 445 | nCrunchTemp_* 446 | 447 | # MightyMoose 448 | *.mm.* 449 | AutoTest.Net/ 450 | 451 | # Web workbench (sass) 452 | .sass-cache/ 453 | 454 | # Installshield output folder 455 | [Ee]xpress/ 456 | 457 | # DocProject is a documentation generator add-in 458 | DocProject/buildhelp/ 459 | DocProject/Help/*.HxT 460 | DocProject/Help/*.HxC 461 | DocProject/Help/*.hhc 462 | DocProject/Help/*.hhk 463 | DocProject/Help/*.hhp 464 | DocProject/Help/Html2 465 | DocProject/Help/html 466 | 467 | # Click-Once directory 468 | publish/ 469 | 470 | # Publish Web Output 471 | *.[Pp]ublish.xml 472 | *.azurePubxml 473 | # Note: Comment the next line if you want to checkin your web deploy settings, 474 | # but database connection strings (with potential passwords) will be unencrypted 475 | *.pubxml 476 | *.publishproj 477 | 478 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 479 | # checkin your Azure Web App publish settings, but sensitive information contained 480 | # in these scripts will be unencrypted 481 | PublishScripts/ 482 | 483 | # NuGet Packages 484 | *.nupkg 485 | # NuGet Symbol Packages 486 | *.snupkg 487 | # The packages folder can be ignored because of Package Restore 488 | **/[Pp]ackages/* 489 | # except build/, which is used as an MSBuild target. 490 | !**/[Pp]ackages/build/ 491 | # Uncomment if necessary however generally it will be regenerated when needed 492 | #!**/[Pp]ackages/repositories.config 493 | # NuGet v3's project.json files produces more ignorable files 494 | *.nuget.props 495 | *.nuget.targets 496 | 497 | # Microsoft Azure Build Output 498 | csx/ 499 | *.build.csdef 500 | 501 | # Microsoft Azure Emulator 502 | ecf/ 503 | rcf/ 504 | 505 | # Windows Store app package directories and files 506 | AppPackages/ 507 | BundleArtifacts/ 508 | Package.StoreAssociation.xml 509 | _pkginfo.txt 510 | *.appx 511 | *.appxbundle 512 | *.appxupload 513 | 514 | # Visual Studio cache files 515 | # files ending in .cache can be ignored 516 | *.[Cc]ache 517 | # but keep track of directories ending in .cache 518 | !?*.[Cc]ache/ 519 | 520 | # Others 521 | ClientBin/ 522 | ~$* 523 | *.dbmdl 524 | *.dbproj.schemaview 525 | *.jfm 526 | *.pfx 527 | *.publishsettings 528 | orleans.codegen.cs 529 | 530 | # Including strong name files can present a security risk 531 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 532 | #*.snk 533 | 534 | # Since there are multiple workflows, uncomment next line to ignore bower_components 535 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 536 | #bower_components/ 537 | 538 | # RIA/Silverlight projects 539 | Generated_Code/ 540 | 541 | # Backup & report files from converting an old project file 542 | # to a newer Visual Studio version. Backup files are not needed, 543 | # because we have git ;-) 544 | _UpgradeReport_Files/ 545 | Backup*/ 546 | UpgradeLog*.XML 547 | UpgradeLog*.htm 548 | ServiceFabricBackup/ 549 | *.rptproj.bak 550 | 551 | # SQL Server files 552 | *.mdf 553 | *.ldf 554 | *.ndf 555 | 556 | # Business Intelligence projects 557 | *.rdl.data 558 | *.bim.layout 559 | *.bim_*.settings 560 | *.rptproj.rsuser 561 | *- [Bb]ackup.rdl 562 | *- [Bb]ackup ([0-9]).rdl 563 | *- [Bb]ackup ([0-9][0-9]).rdl 564 | 565 | # Microsoft Fakes 566 | FakesAssemblies/ 567 | 568 | # GhostDoc plugin setting file 569 | *.GhostDoc.xml 570 | 571 | # Node.js Tools for Visual Studio 572 | .ntvs_analysis.dat 573 | node_modules/ 574 | 575 | # Visual Studio 6 build log 576 | *.plg 577 | 578 | # Visual Studio 6 workspace options file 579 | *.opt 580 | 581 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 582 | *.vbw 583 | 584 | # Visual Studio LightSwitch build output 585 | **/*.HTMLClient/GeneratedArtifacts 586 | **/*.DesktopClient/GeneratedArtifacts 587 | **/*.DesktopClient/ModelManifest.xml 588 | **/*.Server/GeneratedArtifacts 589 | **/*.Server/ModelManifest.xml 590 | _Pvt_Extensions 591 | 592 | # Paket dependency manager 593 | .paket/paket.exe 594 | paket-files/ 595 | 596 | # FAKE - F# Make 597 | .fake/ 598 | 599 | # CodeRush personal settings 600 | .cr/personal 601 | 602 | # Python Tools for Visual Studio (PTVS) 603 | __pycache__/ 604 | *.pyc 605 | 606 | # Cake - Uncomment if you are using it 607 | # tools/** 608 | # !tools/packages.config 609 | 610 | # Tabs Studio 611 | *.tss 612 | 613 | # Telerik's JustMock configuration file 614 | *.jmconfig 615 | 616 | # BizTalk build output 617 | *.btp.cs 618 | *.btm.cs 619 | *.odx.cs 620 | *.xsd.cs 621 | 622 | # OpenCover UI analysis results 623 | OpenCover/ 624 | 625 | # Azure Stream Analytics local run output 626 | ASALocalRun/ 627 | 628 | # MSBuild Binary and Structured Log 629 | *.binlog 630 | 631 | # NVidia Nsight GPU debugger configuration file 632 | *.nvuser 633 | 634 | # MFractors (Xamarin productivity tool) working folder 635 | .mfractor/ 636 | 637 | # Local History for Visual Studio 638 | .localhistory/ 639 | 640 | # BeatPulse healthcheck temp database 641 | healthchecksdb 642 | 643 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 644 | MigrationBackup/ 645 | 646 | # Ionide (cross platform F# VS Code tools) working folder 647 | .ionide/ 648 | 649 | # End of https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ManagedCode 5 | Copyright © 2021-$([System.DateTime]::Now.ToString(`yyyy`)) ManagedCode SAS 6 | true 7 | true 8 | true 9 | snupkg 10 | Github 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | logo.png 13 | MIT 14 | true 15 | README.md 16 | 17 | https://github.com/managedcode/TimeSeries 18 | https://github.com/managedcode/TimeSeries 19 | Managed Code - TimeSeries 20 | 0.0.18 21 | 0.0.18 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Managed-Code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Benchmark/Benchmarks/Bench1.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using ManagedCode.TimeSeries.Accumulators; 3 | 4 | namespace ManagedCode.TimeSeries.Benchmark.Benchmarks; 5 | 6 | 7 | [SimpleJob] 8 | [MemoryDiagnoser] 9 | public class Bench1 10 | { 11 | [Benchmark] 12 | public async Task Int_1000() 13 | { 14 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(50)); 15 | for (var i = 0; i < 1000; i++) 16 | { 17 | series.AddNewData(i); 18 | } 19 | } 20 | 21 | [Benchmark] 22 | public async Task Int_100_000() 23 | { 24 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(50)); 25 | for (var i = 0; i < 100_000; i++) 26 | { 27 | series.AddNewData(i); 28 | } 29 | } 30 | 31 | [Benchmark] 32 | public async Task Int_10_000_000() 33 | { 34 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(50)); 35 | for (var i = 0; i < 10_000_000; i++) 36 | { 37 | series.AddNewData(i); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Benchmark/Benchmarks/CollectionBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using ManagedCode.TimeSeries.Accumulators; 3 | 4 | namespace ManagedCode.TimeSeries.Benchmark.Benchmarks; 5 | 6 | 7 | [SimpleJob] 8 | [MemoryDiagnoser] 9 | public class CollectionBenchmark 10 | { 11 | [Benchmark] 12 | public void Queue_1000() 13 | { 14 | var series = new Queue(); 15 | for (var i = 0; i < 1000; i++) 16 | { 17 | series.Enqueue(i); 18 | if (series.Count > 100) 19 | series.Dequeue(); 20 | } 21 | } 22 | 23 | [Benchmark] 24 | public void Queue_100_000() 25 | { 26 | var series = new Queue(); 27 | for (var i = 0; i < 100_000; i++) 28 | { 29 | series.Enqueue(i); 30 | if (series.Count > 100) 31 | series.Dequeue(); 32 | } 33 | } 34 | 35 | [Benchmark] 36 | public void Queue_10_000_000() 37 | { 38 | var series = new Queue(); 39 | for (var i = 0; i < 10_000_000; i++) 40 | { 41 | series.Enqueue(i); 42 | if (series.Count > 100) 43 | series.Dequeue(); 44 | } 45 | } 46 | 47 | [Benchmark] 48 | public void LinkedList_1000() 49 | { 50 | var series = new LinkedList(); 51 | for (var i = 0; i < 1000; i++) 52 | { 53 | series.AddLast(i); 54 | if (series.Count > 100) 55 | series.RemoveFirst(); 56 | } 57 | } 58 | 59 | [Benchmark] 60 | public void LinkedList_100_000() 61 | { 62 | var series = new LinkedList(); 63 | for (var i = 0; i < 100_000; i++) 64 | { 65 | series.AddLast(i); 66 | if (series.Count > 100) 67 | series.RemoveFirst(); 68 | } 69 | } 70 | 71 | [Benchmark] 72 | public void LinkedList_10_000_000() 73 | { 74 | var series = new LinkedList(); 75 | for (var i = 0; i < 10_000_000; i++) 76 | { 77 | series.AddLast(i); 78 | if (series.Count > 100) 79 | series.RemoveFirst(); 80 | } 81 | } 82 | 83 | [Benchmark] 84 | public void List_1000() 85 | { 86 | var series = new List(); 87 | for (var i = 0; i < 1000; i++) 88 | { 89 | series.Add(i); 90 | if (series.Count > 100) 91 | series.RemoveAt(0); 92 | } 93 | } 94 | 95 | [Benchmark] 96 | public void List_100_000() 97 | { 98 | var series = new List(); 99 | for (var i = 0; i < 100_000; i++) 100 | { 101 | series.Add(i); 102 | if (series.Count > 100) 103 | series.RemoveAt(0); 104 | } 105 | } 106 | 107 | [Benchmark] 108 | public void List_10_000_000() 109 | { 110 | var series = new List(); 111 | for (var i = 0; i < 10_000_000; i++) 112 | { 113 | series.Add(i); 114 | if (series.Count > 100) 115 | series.RemoveAt(0); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Benchmark/ManagedCode.TimeSeries.Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | 11 7 | enable 8 | enable 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | using BenchmarkDotNet.Running; 3 | 4 | var summary = BenchmarkRunner.Run(typeof(Program).Assembly); -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Accumulators/Converters/DoubleTimeSeriesAccumulatorConverter.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Accumulators; 2 | using Orleans; 3 | 4 | namespace ManagedCode.TimeSeries.Orleans; 5 | 6 | [RegisterConverter] 7 | public sealed class DoubleTimeSeriesAccumulatorConverter : IConverter> 8 | { 9 | public DoubleTimeSeriesAccumulator ConvertFromSurrogate(in TimeSeriesAccumulatorsSurrogate surrogate) 10 | { 11 | var series = new DoubleTimeSeriesAccumulator(surrogate.SampleInterval, surrogate.MaxSamplesCount); 12 | series.InitInternal(surrogate.Samples, surrogate.Start, surrogate.End, surrogate.LastDate, surrogate.DataCount); 13 | return series; 14 | } 15 | 16 | public TimeSeriesAccumulatorsSurrogate ConvertToSurrogate(in DoubleTimeSeriesAccumulator value) 17 | { 18 | return new TimeSeriesAccumulatorsSurrogate(value.Samples, value.Start, value.End, 19 | value.SampleInterval, value.MaxSamplesCount, value.LastDate, value.DataCount); 20 | } 21 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Accumulators/Converters/FloatTimeSeriesAccumulatorConverter.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Accumulators; 2 | using Orleans; 3 | 4 | namespace ManagedCode.TimeSeries.Orleans; 5 | 6 | [RegisterConverter] 7 | public sealed class FloatTimeSeriesAccumulatorConverter : IConverter> 8 | { 9 | public FloatTimeSeriesAccumulator ConvertFromSurrogate(in TimeSeriesAccumulatorsSurrogate surrogate) 10 | { 11 | var series = new FloatTimeSeriesAccumulator(surrogate.SampleInterval, surrogate.MaxSamplesCount); 12 | series.InitInternal(surrogate.Samples, surrogate.Start, surrogate.End, surrogate.LastDate, surrogate.DataCount); 13 | return series; 14 | } 15 | 16 | public TimeSeriesAccumulatorsSurrogate ConvertToSurrogate(in FloatTimeSeriesAccumulator value) 17 | { 18 | return new TimeSeriesAccumulatorsSurrogate(value.Samples, value.Start, value.End, 19 | value.SampleInterval, value.MaxSamplesCount, value.LastDate, value.DataCount); 20 | } 21 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Accumulators/Converters/IntTimeSeriesAccumulatorConverter.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | using ManagedCode.TimeSeries.Accumulators; 3 | using ManagedCode.TimeSeries.Summers; 4 | using Orleans; 5 | 6 | namespace ManagedCode.TimeSeries.Orleans; 7 | 8 | // This is a converter which converts between the surrogate and the foreign type. 9 | [RegisterConverter] 10 | public sealed class IntTimeSeriesAccumulatorConverter : IConverter> 11 | { 12 | public IntTimeSeriesAccumulator ConvertFromSurrogate(in TimeSeriesAccumulatorsSurrogate surrogate) 13 | { 14 | var series = new IntTimeSeriesAccumulator(surrogate.SampleInterval, surrogate.MaxSamplesCount); 15 | series.InitInternal(surrogate.Samples, surrogate.Start, surrogate.End, surrogate.LastDate, surrogate.DataCount); 16 | return series; 17 | } 18 | 19 | public TimeSeriesAccumulatorsSurrogate ConvertToSurrogate(in IntTimeSeriesAccumulator value) 20 | { 21 | return new TimeSeriesAccumulatorsSurrogate(value.Samples, value.Start, value.End, 22 | value.SampleInterval, value.MaxSamplesCount, value.LastDate, value.DataCount); 23 | } 24 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Accumulators/TimeSeriesAccumulatorsSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | using System.Runtime.InteropServices.JavaScript; 5 | using ManagedCode.TimeSeries.Abstractions; 6 | using Orleans; 7 | 8 | namespace ManagedCode.TimeSeries.Orleans; 9 | 10 | // This is the surrogate which will act as a stand-in for the foreign type. 11 | // Surrogates should use plain fields instead of properties for better perfomance. 12 | [Immutable] 13 | [GenerateSerializer] 14 | public struct TimeSeriesAccumulatorsSurrogate 15 | { 16 | public TimeSeriesAccumulatorsSurrogate(Dictionary> samples, 17 | DateTimeOffset start, 18 | DateTimeOffset end, 19 | TimeSpan sampleInterval, 20 | int maxSamplesCount, 21 | DateTimeOffset lastDate, 22 | ulong dataCount) 23 | { 24 | Samples = samples; 25 | Start = start; 26 | End = end; 27 | SampleInterval = sampleInterval; 28 | MaxSamplesCount = maxSamplesCount; 29 | LastDate = lastDate; 30 | DataCount = dataCount; 31 | } 32 | 33 | [Id(0)] 34 | public Dictionary> Samples; 35 | [Id(1)] 36 | public DateTimeOffset Start; 37 | [Id(2)] 38 | public DateTimeOffset End; 39 | [Id(3)] 40 | public TimeSpan SampleInterval; 41 | [Id(4)] 42 | public int MaxSamplesCount; 43 | [Id(5)] 44 | public DateTimeOffset LastDate; 45 | [Id(6)] 46 | public ulong DataCount; 47 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/ManagedCode.TimeSeries.Orleans.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 5 | enable 6 | true 7 | net7.0 8 | 9 | 10 | 11 | 12 | ManagedCode.TimeSeries.Orleans 13 | ManagedCode.TimeSeries.Orleans 14 | TimeSeries 15 | managedcode, TimeSeries, Orleans 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Summers/Converters/DoubleTimeSeriesSummerConverter.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Summers; 2 | using Orleans; 3 | 4 | namespace ManagedCode.TimeSeries.Orleans; 5 | 6 | [RegisterConverter] 7 | public sealed class DoubleTimeSeriesSummerConverter : IConverter> 8 | { 9 | public DoubleTimeSeriesSummer ConvertFromSurrogate(in TimeSeriesSummerSurrogate surrogate) 10 | { 11 | var series = new DoubleTimeSeriesSummer(surrogate.SampleInterval, surrogate.MaxSamplesCount, surrogate.Strategy); 12 | series.InitInternal(surrogate.Samples, surrogate.Start, surrogate.End, surrogate.LastDate, surrogate.DataCount); 13 | return series; 14 | } 15 | 16 | public TimeSeriesSummerSurrogate ConvertToSurrogate(in DoubleTimeSeriesSummer value) 17 | { 18 | return new TimeSeriesSummerSurrogate(value.Samples, value.Start, value.End, 19 | value.SampleInterval, value.MaxSamplesCount, value.LastDate, value.DataCount, value.Strategy); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Summers/Converters/FloatTimeSeriesSummerConverter.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Summers; 2 | using Orleans; 3 | 4 | namespace ManagedCode.TimeSeries.Orleans; 5 | 6 | [RegisterConverter] 7 | public sealed class FloatTimeSeriesSummerConverter : IConverter> 8 | { 9 | public FloatTimeSeriesSummer ConvertFromSurrogate(in TimeSeriesSummerSurrogate surrogate) 10 | { 11 | var series = new FloatTimeSeriesSummer(surrogate.SampleInterval, surrogate.MaxSamplesCount, surrogate.Strategy); 12 | series.InitInternal(surrogate.Samples, surrogate.Start, surrogate.End, surrogate.LastDate, surrogate.DataCount); 13 | return series; 14 | } 15 | 16 | public TimeSeriesSummerSurrogate ConvertToSurrogate(in FloatTimeSeriesSummer value) 17 | { 18 | return new TimeSeriesSummerSurrogate(value.Samples, value.Start, value.End, 19 | value.SampleInterval, value.MaxSamplesCount, value.LastDate, value.DataCount, value.Strategy); 20 | } 21 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Summers/Converters/IntTimeSeriesSummerConverter.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Summers; 2 | using Orleans; 3 | 4 | namespace ManagedCode.TimeSeries.Orleans; 5 | 6 | // This is a converter which converts between the surrogate and the foreign type. 7 | [RegisterConverter] 8 | public sealed class IntTimeSeriesSummerConverter : IConverter> 9 | { 10 | public IntTimeSeriesSummer ConvertFromSurrogate(in TimeSeriesSummerSurrogate surrogate) 11 | { 12 | var series = new IntTimeSeriesSummer(surrogate.SampleInterval, surrogate.MaxSamplesCount, surrogate.Strategy); 13 | series.InitInternal(surrogate.Samples, surrogate.Start, surrogate.End, surrogate.LastDate, surrogate.DataCount); 14 | return series; 15 | } 16 | 17 | public TimeSeriesSummerSurrogate ConvertToSurrogate(in IntTimeSeriesSummer value) 18 | { 19 | return new TimeSeriesSummerSurrogate(value.Samples, value.Start, value.End, 20 | value.SampleInterval, value.MaxSamplesCount, value.LastDate, value.DataCount, value.Strategy); 21 | } 22 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Orleans/Summers/TimeSeriesSummerSurrogate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | using System.Runtime.InteropServices.JavaScript; 5 | using ManagedCode.TimeSeries.Abstractions; 6 | using Orleans; 7 | 8 | namespace ManagedCode.TimeSeries.Orleans; 9 | 10 | // This is the surrogate which will act as a stand-in for the foreign type. 11 | // Surrogates should use plain fields instead of properties for better perfomance. 12 | [Immutable] 13 | [GenerateSerializer] 14 | public struct TimeSeriesSummerSurrogate 15 | { 16 | public TimeSeriesSummerSurrogate(Dictionary samples, 17 | DateTimeOffset start, 18 | DateTimeOffset end, 19 | TimeSpan sampleInterval, 20 | int maxSamplesCount, 21 | DateTimeOffset lastDate, 22 | ulong dataCount, 23 | Strategy strategy) 24 | { 25 | Samples = samples; 26 | Start = start; 27 | End = end; 28 | SampleInterval = sampleInterval; 29 | MaxSamplesCount = maxSamplesCount; 30 | LastDate = lastDate; 31 | DataCount = dataCount; 32 | Strategy = strategy; 33 | } 34 | 35 | [Id(0)] 36 | public Dictionary Samples; 37 | [Id(1)] 38 | public DateTimeOffset Start; 39 | [Id(2)] 40 | public DateTimeOffset End; 41 | [Id(3)] 42 | public TimeSpan SampleInterval; 43 | [Id(4)] 44 | public int MaxSamplesCount; 45 | [Id(5)] 46 | public DateTimeOffset LastDate; 47 | [Id(6)] 48 | public ulong DataCount; 49 | [Id(7)] 50 | public Strategy Strategy; 51 | 52 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Tests/AccumulatorsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using ManagedCode.TimeSeries.Accumulators; 3 | using Xunit; 4 | 5 | namespace ManagedCode.TimeSeries.Tests; 6 | 7 | public class AccumulatorsTests 8 | { 9 | 10 | [Fact] 11 | public async Task IntTimeSeriesAccumulator() 12 | { 13 | int count = 1050; 14 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1)); 15 | for (int i = 0; i < count; i++) 16 | { 17 | await Task.Delay(new Random().Next(1, 5)); 18 | series.AddNewData(i); 19 | } 20 | 21 | series.DataCount.Should().Be(Convert.ToUInt64(count)); 22 | 23 | var step = 0; 24 | foreach (var queue in series.Samples) 25 | { 26 | foreach (var item in queue.Value) 27 | { 28 | item.Should().Be(step); 29 | step++; 30 | } 31 | } 32 | } 33 | 34 | [Fact(Skip = "Need fix")] 35 | public async Task IntTimeSeriesAccumulatorMaxSamplesCount() 36 | { 37 | int samplesCount = 105; 38 | int count = 1050; 39 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(0.1), samplesCount); 40 | for (int i = 0; i < count; i++) 41 | { 42 | await Task.Delay(new Random().Next(1, 5)); 43 | series.AddNewData(i); 44 | } 45 | 46 | series.DataCount.Should().Be(Convert.ToUInt64(count)); //because it's total; number of samples 47 | series.Samples.Count.Should().Be(samplesCount); //because it's total; number of samples 48 | 49 | var step = count - samplesCount - 1; 50 | foreach (var queue in series.Samples) 51 | { 52 | foreach (var item in queue.Value) 53 | { 54 | item.Should().Be(step); 55 | step++; 56 | } 57 | } 58 | } 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | [Fact] 72 | public async Task Accumulator() 73 | { 74 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1)); 75 | for (var i = 0; i < 1000; i++) 76 | { 77 | await Task.Delay(new Random().Next(1, 5)); 78 | series.AddNewData(i); 79 | } 80 | 81 | series.DataCount.Should().Be(1000); 82 | 83 | var step = 0; 84 | foreach (var queue in series.Samples) 85 | { 86 | foreach (var item in queue.Value) 87 | { 88 | item.Should().Be(step); 89 | step++; 90 | } 91 | } 92 | } 93 | 94 | // [Fact] 95 | // public async Task AccumulatorByString() 96 | // { 97 | // var rnd = new Random(); 98 | // var series = new StringTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1)); 99 | // 100 | // var dt = DateTimeOffset.Now; 101 | // series.AddNewData(dt, "1"); 102 | // series.AddNewData(dt, "1"); 103 | // series.AddNewData(dt, "2"); 104 | // series.AddNewData(dt, "3"); 105 | // series.AddNewData(dt, "3"); 106 | // series.AddNewData(dt, "2"); 107 | // 108 | // 109 | // dt = dt.AddHours(5); 110 | // series.AddNewData(dt, "1"); 111 | // series.AddNewData(dt, "1"); 112 | // series.AddNewData(dt, "2"); 113 | // series.AddNewData(dt, "3"); 114 | // series.AddNewData(dt, "3"); 115 | // series.AddNewData(dt, "2"); 116 | // 117 | // series.DataCount.Should().Be(12); 118 | // series.Samples.First().Value.Count.Should().Be(3); 119 | // series.Samples.Last().Value.Count.Should().Be(3); 120 | // } 121 | 122 | [Fact] 123 | public async Task AccumulatorLimit() 124 | { 125 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 126 | 127 | for (var i = 0; i < 1000; i++) 128 | { 129 | await Task.Delay(new Random().Next(1, 5)); 130 | series.AddNewData(i); 131 | } 132 | 133 | series.Samples.Count.Should().Be(10); 134 | } 135 | 136 | [Fact] 137 | public async Task IsFull() 138 | { 139 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 140 | 141 | for (var i = 0; i < 1000; i++) 142 | { 143 | await Task.Delay(new Random().Next(1, 5)); 144 | 145 | if (series.IsFull) 146 | { 147 | break; 148 | } 149 | 150 | series.AddNewData(i); 151 | } 152 | 153 | series.IsFull.Should().BeTrue(); 154 | } 155 | 156 | [Fact] 157 | public void IsEmpty() 158 | { 159 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 160 | series.IsEmpty.Should().BeTrue(); 161 | } 162 | 163 | [Fact] 164 | public async Task AccumulatorMerge() 165 | { 166 | Func> FillFunc = async () => 167 | { 168 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 169 | for (var i = 0; i < 1000; i++) 170 | { 171 | await Task.Delay(new Random().Next(1, 5)); 172 | series.AddNewData(i); 173 | } 174 | 175 | return series; 176 | }; 177 | 178 | var seriesA = FillFunc(); 179 | var seriesB = FillFunc(); 180 | 181 | await Task.WhenAll(seriesA, seriesB); 182 | 183 | seriesA.Result.Samples.Count.Should().Be(10); 184 | seriesB.Result.Samples.Count.Should().Be(10); 185 | 186 | seriesA.Result.Merge(seriesB.Result); 187 | 188 | seriesA.Result.Samples.Count.Should().Be(10); 189 | 190 | var seriesList = new List(); 191 | seriesList.Add(await FillFunc()); 192 | seriesList.Add(await FillFunc()); 193 | seriesList.Add(await FillFunc()); 194 | seriesList.Add(new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10)); 195 | 196 | IntTimeSeriesAccumulator onlineExpertsPerHourTimeSeries = null; 197 | foreach (var item in seriesList.ToArray()) 198 | { 199 | if (onlineExpertsPerHourTimeSeries == null) 200 | { 201 | onlineExpertsPerHourTimeSeries = item; 202 | } 203 | else 204 | { 205 | onlineExpertsPerHourTimeSeries.Merge(item); 206 | } 207 | } 208 | 209 | onlineExpertsPerHourTimeSeries.Samples.Count.Should().Be(10); 210 | } 211 | 212 | 213 | [Fact] 214 | public async Task Resample() 215 | { 216 | var seriesFeature = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(2), 100); 217 | 218 | for (var i = 0; i < 100; i++) 219 | { 220 | seriesFeature.AddNewData(i); 221 | 222 | await Task.Delay(1); 223 | } 224 | 225 | seriesFeature.Resample(TimeSpan.FromMilliseconds(4), 100); 226 | var sad = seriesFeature; 227 | } 228 | 229 | 230 | 231 | [Fact] 232 | public void MarkupAllSamples() 233 | { 234 | var seriesFeature = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(10), 100); 235 | seriesFeature.MarkupAllSamples(MarkupDirection.Feature); 236 | seriesFeature.AddNewData(1); 237 | (seriesFeature.Samples.Keys.Max() - seriesFeature.Samples.Keys.Min()).TotalMilliseconds.Should().BeGreaterThanOrEqualTo(990); 238 | (seriesFeature.Samples.Keys.Max() - seriesFeature.Samples.Keys.Min()).TotalMilliseconds.Should().BeLessThanOrEqualTo(1000); 239 | var seriesFeatureOrdered = seriesFeature.Samples.OrderBy(o => o.Key).Take(10); 240 | seriesFeatureOrdered.Any(a => a.Value.Count == 1).Should().BeTrue(); 241 | 242 | var seriesPast = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(10)); 243 | seriesPast.MarkupAllSamples(); 244 | seriesPast.AddNewData(1); 245 | (seriesPast.Samples.Keys.Max() - seriesPast.Samples.Keys.Min()).TotalMilliseconds.Should().BeGreaterThanOrEqualTo(990); 246 | (seriesPast.Samples.Keys.Max() - seriesPast.Samples.Keys.Min()).TotalMilliseconds.Should().BeLessThanOrEqualTo(1000); 247 | var seriesPastOrdered = seriesPast.Samples.OrderBy(o => o.Key).TakeLast(10); 248 | seriesPastOrdered.Any(a => a.Value.Count == 1).Should().BeTrue(); 249 | 250 | var seriesMiddle = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(10), 100); 251 | seriesMiddle.MarkupAllSamples(MarkupDirection.Middle); 252 | seriesMiddle.AddNewData(1); 253 | (seriesMiddle.Samples.Keys.Max() - seriesMiddle.Samples.Keys.Min()).TotalMilliseconds.Should().BeGreaterThanOrEqualTo(990); 254 | (seriesMiddle.Samples.Keys.Max() - seriesMiddle.Samples.Keys.Min()).TotalMilliseconds.Should().BeLessThanOrEqualTo(1000); 255 | var seriesMiddleOrdered = seriesMiddle.Samples.OrderBy(o => o.Key).Skip(45).Take(10); 256 | seriesMiddleOrdered.Any(a => a.Value.Count == 1).Should().BeTrue(); 257 | } 258 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Tests/ManagedCode.TimeSeries.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 11 6 | enable 7 | enable 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | all 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.Tests/SummersTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using ManagedCode.TimeSeries.Accumulators; 3 | using ManagedCode.TimeSeries.Summers; 4 | using Xunit; 5 | 6 | namespace ManagedCode.TimeSeries.Tests; 7 | 8 | public class SummersTests 9 | { 10 | [Fact] 11 | public async Task Accumulator() 12 | { 13 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1)); 14 | for (var i = 0; i < 1000; i++) 15 | { 16 | await Task.Delay(new Random().Next(1, 5)); 17 | series.AddNewData(i); 18 | } 19 | 20 | series.DataCount.Should().Be(1000); 21 | 22 | var step = 0; 23 | foreach (var queue in series.Samples) 24 | { 25 | foreach (var item in queue.Value) 26 | { 27 | item.Should().Be(step); 28 | step++; 29 | } 30 | } 31 | } 32 | 33 | // [Fact] 34 | // public async Task AccumulatorByString() 35 | // { 36 | // var rnd = new Random(); 37 | // var series = new StringTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1)); 38 | // 39 | // var dt = DateTimeOffset.Now; 40 | // series.AddNewData(dt, "1"); 41 | // series.AddNewData(dt, "1"); 42 | // series.AddNewData(dt, "2"); 43 | // series.AddNewData(dt, "3"); 44 | // series.AddNewData(dt, "3"); 45 | // series.AddNewData(dt, "2"); 46 | // 47 | // 48 | // dt = dt.AddHours(5); 49 | // series.AddNewData(dt, "1"); 50 | // series.AddNewData(dt, "1"); 51 | // series.AddNewData(dt, "2"); 52 | // series.AddNewData(dt, "3"); 53 | // series.AddNewData(dt, "3"); 54 | // series.AddNewData(dt, "2"); 55 | // 56 | // series.DataCount.Should().Be(12); 57 | // series.Samples.First().Value.Count.Should().Be(3); 58 | // series.Samples.Last().Value.Count.Should().Be(3); 59 | // } 60 | 61 | [Fact] 62 | public async Task AccumulatorLimit() 63 | { 64 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 65 | 66 | for (var i = 0; i < 1000; i++) 67 | { 68 | await Task.Delay(new Random().Next(1, 5)); 69 | series.AddNewData(i); 70 | } 71 | 72 | series.Samples.Count.Should().Be(10); 73 | } 74 | 75 | [Fact] 76 | public async Task IsFull() 77 | { 78 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 79 | 80 | for (var i = 0; i < 1000; i++) 81 | { 82 | await Task.Delay(new Random().Next(1, 5)); 83 | 84 | if (series.IsFull) 85 | { 86 | break; 87 | } 88 | 89 | series.AddNewData(i); 90 | } 91 | 92 | series.IsFull.Should().BeTrue(); 93 | } 94 | 95 | [Fact] 96 | public void IsEmpty() 97 | { 98 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 99 | series.IsEmpty.Should().BeTrue(); 100 | } 101 | 102 | [Fact] 103 | public async Task AccumulatorMerge() 104 | { 105 | Func> FillFunc = async () => 106 | { 107 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10); 108 | for (var i = 0; i < 1000; i++) 109 | { 110 | await Task.Delay(new Random().Next(1, 5)); 111 | series.AddNewData(i); 112 | } 113 | 114 | return series; 115 | }; 116 | 117 | var seriesA = FillFunc(); 118 | var seriesB = FillFunc(); 119 | 120 | await Task.WhenAll(seriesA, seriesB); 121 | 122 | seriesA.Result.Samples.Count.Should().Be(10); 123 | seriesB.Result.Samples.Count.Should().Be(10); 124 | 125 | seriesA.Result.Merge(seriesB.Result); 126 | 127 | seriesA.Result.Samples.Count.Should().Be(10); 128 | 129 | var seriesList = new List(); 130 | seriesList.Add(await FillFunc()); 131 | seriesList.Add(await FillFunc()); 132 | seriesList.Add(await FillFunc()); 133 | seriesList.Add(new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1), 10)); 134 | 135 | IntTimeSeriesAccumulator onlineExpertsPerHourTimeSeries = null; 136 | foreach (var item in seriesList.ToArray()) 137 | { 138 | if (onlineExpertsPerHourTimeSeries == null) 139 | { 140 | onlineExpertsPerHourTimeSeries = item; 141 | } 142 | else 143 | { 144 | onlineExpertsPerHourTimeSeries.Merge(item); 145 | } 146 | } 147 | 148 | onlineExpertsPerHourTimeSeries.Samples.Count.Should().Be(10); 149 | } 150 | 151 | [Fact] 152 | public void IntTimeSeriesSummerIncrementDecrement() 153 | { 154 | var series = new IntTimeSeriesSummer(TimeSpan.FromMinutes(1), 10); 155 | for (var i = 0; i < 100; i++) 156 | { 157 | series.Increment(); 158 | } 159 | 160 | for (var i = 0; i < 50; i++) 161 | { 162 | series.Decrement(); 163 | } 164 | 165 | series.DataCount.Should().Be(150); 166 | series.Samples.First().Value.Should().Be(50); 167 | } 168 | 169 | [Fact] 170 | public async Task Summer() 171 | { 172 | var series = new IntTimeSeriesSummer(TimeSpan.FromSeconds(0.1)); 173 | var count = 0; 174 | for (var i = 0; i < 100; i++) 175 | { 176 | await Task.Delay(new Random().Next(10, 50)); 177 | series.AddNewData(i); 178 | count++; 179 | } 180 | 181 | series.DataCount.Should().Be((ulong) count); 182 | } 183 | 184 | // [Fact] 185 | // public async Task SummerGroup() 186 | // { 187 | // var interval = TimeSpan.FromSeconds(0.1); 188 | // var series = new IntGroupTimeSeriesSummer(interval, 100, Strategy.Sum, true); 189 | // var count = 0; 190 | // for (var i = 0; i < 100; i++) 191 | // { 192 | // await Task.Delay(new Random().Next(10, 50)); 193 | // series.AddNewData((i % 10).ToString(), i); 194 | // count++; 195 | // } 196 | // 197 | // series.TimeSeries.Count.Should().BeGreaterThan(0); 198 | // await Task.Delay(interval * 102); 199 | // 200 | // series.TimeSeries.Count.Should().Be(0); 201 | // } 202 | // 203 | // [Fact] 204 | // public async Task SummerGroupMax() 205 | // { 206 | // var interval = TimeSpan.FromSeconds(5); 207 | // var series = new IntGroupTimeSeriesSummer(interval, 100, Strategy.Max, true); 208 | // var count = 0; 209 | // for (var i = 0; i < 100; i++) 210 | // { 211 | // series.AddNewData("host", i); 212 | // count++; 213 | // } 214 | // 215 | // series.TimeSeries.Count.Should().Be(1); 216 | // series.TimeSeries.Values.SingleOrDefault().Samples.SingleOrDefault().Value.Should().Be(99); 217 | // } 218 | 219 | [Fact] 220 | public async Task SummerMerge() 221 | { 222 | Func> FillFunc = async () => 223 | { 224 | var series = new IntTimeSeriesSummer(TimeSpan.FromSeconds(0.1)); 225 | 226 | for (var i = 0; i < 100; i++) 227 | { 228 | await Task.Delay(new Random().Next(10, 50)); 229 | series.AddNewData(1); 230 | } 231 | 232 | return series; 233 | }; 234 | 235 | var seriesA = FillFunc(); 236 | var seriesB = FillFunc(); 237 | 238 | await Task.WhenAll(seriesA, seriesB); 239 | 240 | seriesA.Result.DataCount.Should().Be(100); 241 | seriesB.Result.DataCount.Should().Be(100); 242 | 243 | seriesA.Result.Merge(seriesB.Result); 244 | 245 | seriesA.Result.DataCount.Should().Be(200); 246 | 247 | seriesA.Result.Samples.Select(s => s.Value).Sum().Should().Be(200); 248 | } 249 | 250 | [Fact] 251 | public async Task Resample() 252 | { 253 | var seriesFeature = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(2), 100); 254 | 255 | for (var i = 0; i < 100; i++) 256 | { 257 | seriesFeature.AddNewData(i); 258 | 259 | await Task.Delay(1); 260 | } 261 | 262 | seriesFeature.Resample(TimeSpan.FromMilliseconds(4), 100); 263 | var sad = seriesFeature; 264 | } 265 | 266 | [Fact] 267 | public async Task ResampleSummer() 268 | { 269 | var seriesFeature = new IntTimeSeriesSummer(TimeSpan.FromMilliseconds(2), 100); 270 | 271 | for (var i = 0; i < 100; i++) 272 | { 273 | seriesFeature.AddNewData(i); 274 | 275 | await Task.Delay(1); 276 | } 277 | 278 | seriesFeature.Resample(TimeSpan.FromMilliseconds(4), 100); 279 | } 280 | 281 | 282 | [Fact] 283 | public void MarkupAllSamples() 284 | { 285 | var seriesFeature = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(10), 100); 286 | seriesFeature.MarkupAllSamples(MarkupDirection.Feature); 287 | seriesFeature.AddNewData(1); 288 | (seriesFeature.Samples.Keys.Max() - seriesFeature.Samples.Keys.Min()).TotalMilliseconds.Should().BeGreaterThanOrEqualTo(990); 289 | (seriesFeature.Samples.Keys.Max() - seriesFeature.Samples.Keys.Min()).TotalMilliseconds.Should().BeLessThanOrEqualTo(1000); 290 | var seriesFeatureOrdered = seriesFeature.Samples.OrderBy(o => o.Key).Take(10); 291 | seriesFeatureOrdered.Any(a => a.Value.Count == 1).Should().BeTrue(); 292 | 293 | var seriesPast = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(10)); 294 | seriesPast.MarkupAllSamples(); 295 | seriesPast.AddNewData(1); 296 | (seriesPast.Samples.Keys.Max() - seriesPast.Samples.Keys.Min()).TotalMilliseconds.Should().BeGreaterThanOrEqualTo(990); 297 | (seriesPast.Samples.Keys.Max() - seriesPast.Samples.Keys.Min()).TotalMilliseconds.Should().BeLessThanOrEqualTo(1000); 298 | var seriesPastOrdered = seriesPast.Samples.OrderBy(o => o.Key).TakeLast(10); 299 | seriesPastOrdered.Any(a => a.Value.Count == 1).Should().BeTrue(); 300 | 301 | var seriesMiddle = new IntTimeSeriesAccumulator(TimeSpan.FromMilliseconds(10), 100); 302 | seriesMiddle.MarkupAllSamples(MarkupDirection.Middle); 303 | seriesMiddle.AddNewData(1); 304 | (seriesMiddle.Samples.Keys.Max() - seriesMiddle.Samples.Keys.Min()).TotalMilliseconds.Should().BeGreaterThanOrEqualTo(990); 305 | (seriesMiddle.Samples.Keys.Max() - seriesMiddle.Samples.Keys.Min()).TotalMilliseconds.Should().BeLessThanOrEqualTo(1000); 306 | var seriesMiddleOrdered = seriesMiddle.Samples.OrderBy(o => o.Key).Skip(45).Take(10); 307 | seriesMiddleOrdered.Any(a => a.Value.Count == 1).Should().BeTrue(); 308 | } 309 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.TimeSeries", "ManagedCode.TimeSeries\ManagedCode.TimeSeries.csproj", "{76A0D190-83D9-41EE-8086-B01502CE9AAC}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.TimeSeries.Tests", "ManagedCode.TimeSeries.Tests\ManagedCode.TimeSeries.Tests.csproj", "{507FA370-F823-47AA-B304-8F623B3B10F1}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.TimeSeries.Benchmark", "ManagedCode.TimeSeries.Benchmark\ManagedCode.TimeSeries.Benchmark.csproj", "{8A7D8630-5F54-4B88-89BD-3385F4FA01AA}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.TimeSeries.Orleans", "ManagedCode.TimeSeries.Orleans\ManagedCode.TimeSeries.Orleans.csproj", "{30AFA0FC-C87B-411F-B71C-35C92ECDF401}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {76A0D190-83D9-41EE-8086-B01502CE9AAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {76A0D190-83D9-41EE-8086-B01502CE9AAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {76A0D190-83D9-41EE-8086-B01502CE9AAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {76A0D190-83D9-41EE-8086-B01502CE9AAC}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {507FA370-F823-47AA-B304-8F623B3B10F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {507FA370-F823-47AA-B304-8F623B3B10F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {507FA370-F823-47AA-B304-8F623B3B10F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {507FA370-F823-47AA-B304-8F623B3B10F1}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {8A7D8630-5F54-4B88-89BD-3385F4FA01AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {8A7D8630-5F54-4B88-89BD-3385F4FA01AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {8A7D8630-5F54-4B88-89BD-3385F4FA01AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {8A7D8630-5F54-4B88-89BD-3385F4FA01AA}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {30AFA0FC-C87B-411F-B71C-35C92ECDF401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {30AFA0FC-C87B-411F-B71C-35C92ECDF401}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {30AFA0FC-C87B-411F-B71C-35C92ECDF401}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {30AFA0FC-C87B-411F-B71C-35C92ECDF401}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/BaseGroupNumberTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace ManagedCode.TimeSeries.Abstractions; 4 | 5 | public abstract class BaseGroupNumberTimeSeriesSummer : IDisposable 6 | where TSummer : BaseNumberTimeSeriesSummer 7 | where TNumber : INumber 8 | where TSelf : BaseGroupNumberTimeSeriesSummer 9 | { 10 | internal readonly Timer? _timer; 11 | public readonly Dictionary TimeSeries = new(); 12 | 13 | protected BaseGroupNumberTimeSeriesSummer(TimeSpan sampleInterval, bool deleteOverdueSamples) 14 | { 15 | _timer = deleteOverdueSamples ? new Timer(Callback, null, sampleInterval, sampleInterval) : null; 16 | } 17 | 18 | private void Callback(object? state) 19 | { 20 | foreach (var summer in TimeSeries.ToArray()) 21 | { 22 | summer.Value.DeleteOverdueSamples(); 23 | lock (TimeSeries) 24 | { 25 | if (summer.Value.IsEmpty) 26 | { 27 | TimeSeries.Remove(summer.Key); 28 | } 29 | } 30 | } 31 | } 32 | 33 | public virtual void AddNewData(string key, TNumber value) 34 | { 35 | lock (TimeSeries) 36 | { 37 | if (TimeSeries.TryGetValue(key, out var summer)) 38 | { 39 | summer.AddNewData(value); 40 | } 41 | else 42 | { 43 | var newSummer = CreateSummer(); 44 | newSummer.AddNewData(value); 45 | TimeSeries[key] = newSummer; 46 | } 47 | } 48 | } 49 | 50 | public virtual void Increment(string key) 51 | { 52 | lock (TimeSeries) 53 | { 54 | if (TimeSeries.TryGetValue(key, out var summer)) 55 | { 56 | summer.Increment(); 57 | } 58 | else 59 | { 60 | var newSummer = CreateSummer(); 61 | newSummer.Increment(); 62 | TimeSeries[key] = newSummer; 63 | } 64 | } 65 | } 66 | 67 | public virtual void Decrement(string key) 68 | { 69 | lock (TimeSeries) 70 | { 71 | if (TimeSeries.TryGetValue(key, out var summer)) 72 | { 73 | summer.Decrement(); 74 | } 75 | else 76 | { 77 | var newSummer = CreateSummer(); 78 | newSummer.Decrement(); 79 | TimeSeries[key] = newSummer; 80 | } 81 | } 82 | } 83 | 84 | public abstract TNumber Average(); 85 | 86 | public abstract TNumber Min(); 87 | 88 | public abstract TNumber Max(); 89 | 90 | public abstract TNumber Sum(); 91 | 92 | protected abstract TSummer CreateSummer(); 93 | 94 | public void Dispose() 95 | { 96 | _timer?.Dispose(); 97 | } 98 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/BaseNumberTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace ManagedCode.TimeSeries.Abstractions; 5 | 6 | public abstract class BaseNumberTimeSeriesSummer : BaseTimeSeries 7 | where TNumber : INumber where TSelf : BaseTimeSeries 8 | { 9 | protected BaseNumberTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount, Strategy strategy) : base(sampleInterval, maxSamplesCount) 10 | { 11 | Strategy = strategy; 12 | } 13 | 14 | public Strategy Strategy { get; protected set; } 15 | 16 | protected override void AddData(DateTimeOffset date, TNumber data) 17 | { 18 | if (!Samples.ContainsKey(date)) 19 | { 20 | Samples.Add(date, TNumber.Zero); 21 | } 22 | 23 | Samples[date] = Update(Samples[date], data); 24 | } 25 | 26 | public override void Merge(TSelf accumulator) 27 | { 28 | DataCount += accumulator.DataCount; 29 | foreach (var sample in accumulator.Samples.ToArray()) 30 | { 31 | if (Samples.ContainsKey(sample.Key)) 32 | { 33 | Samples[sample.Key] = Update(Samples[sample.Key], sample.Value); 34 | } 35 | else 36 | { 37 | Samples.Add(sample.Key, sample.Value); 38 | } 39 | } 40 | 41 | CheckSamplesSize(); 42 | } 43 | 44 | public override void Resample(TimeSpan sampleInterval, int samplesCount) 45 | { 46 | if (sampleInterval <= SampleInterval) 47 | { 48 | throw new InvalidOperationException(); 49 | } 50 | 51 | SampleInterval = sampleInterval; 52 | MaxSamplesCount = samplesCount; 53 | 54 | var samples = Samples; 55 | 56 | Samples = new Dictionary(); 57 | 58 | foreach (var (key, value) in samples) 59 | { 60 | AddNewData(key, value); 61 | } 62 | } 63 | 64 | private TNumber Update(TNumber left, TNumber right) 65 | { 66 | return Strategy switch 67 | { 68 | Strategy.Sum => left + right, 69 | Strategy.Min => TNumber.Min(left, right), 70 | Strategy.Max => TNumber.Max(left, right), 71 | Strategy.Replace => right, 72 | _ => throw new ArgumentOutOfRangeException() 73 | }; 74 | } 75 | 76 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 77 | public virtual void Increment() 78 | { 79 | AddNewData(TNumber.One); 80 | } 81 | 82 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 | public virtual void Decrement() 84 | { 85 | AddNewData(-TNumber.One); 86 | } 87 | 88 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 89 | public virtual TNumber Average() 90 | { 91 | var sum = Sum(); 92 | return TNumber.CreateChecked(sum) / TNumber.CreateChecked(Samples.Count); 93 | } 94 | 95 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 96 | public virtual TNumber? Min() 97 | { 98 | lock (Samples) 99 | { 100 | return Samples.Values.Min(); 101 | } 102 | } 103 | 104 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 105 | public virtual TNumber? Max() 106 | { 107 | lock (Samples) 108 | { 109 | return Samples.Values.Max(); 110 | } 111 | } 112 | 113 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 114 | public virtual TNumber Sum() 115 | { 116 | lock (Samples) 117 | { 118 | return Samples.Aggregate(TNumber.Zero, (current, sample) => current + sample.Value); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/BaseTimeSeries.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using ManagedCode.TimeSeries.Extensions; 3 | 4 | namespace ManagedCode.TimeSeries.Abstractions; 5 | 6 | public abstract class BaseTimeSeries : 7 | ITimeSeries where TSelf : BaseTimeSeries 8 | { 9 | private const int DefaultSampleCount = 100; 10 | private readonly object _sync = new(); 11 | 12 | protected BaseTimeSeries(TimeSpan sampleInterval, int maxSamplesCount) 13 | { 14 | MaxSamplesCount = maxSamplesCount; 15 | SampleInterval = sampleInterval; 16 | Start = DateTimeOffset.UtcNow.Round(SampleInterval); 17 | End = Start; 18 | } 19 | 20 | protected BaseTimeSeries(TimeSpan sampleInterval, int maxSamplesCount, DateTimeOffset start, DateTimeOffset end, DateTimeOffset lastDate) 21 | { 22 | MaxSamplesCount = maxSamplesCount; 23 | SampleInterval = sampleInterval; 24 | Start = start.Round(SampleInterval); 25 | End = end.Round(SampleInterval); 26 | LastDate = lastDate; 27 | } 28 | 29 | public Dictionary Samples { get; protected set; } = new(); 30 | public DateTimeOffset Start { get; internal set; } 31 | public DateTimeOffset End { get; protected set; } 32 | 33 | public TimeSpan SampleInterval { get; protected set; } 34 | public int MaxSamplesCount { get; protected set; } 35 | 36 | public DateTimeOffset LastDate { get; protected set; } 37 | 38 | public ulong DataCount { get; protected set; } 39 | 40 | internal void InitInternal(Dictionary samples, 41 | DateTimeOffset start , DateTimeOffset end, 42 | DateTimeOffset lastDate, 43 | ulong dataCount) 44 | { 45 | Samples = samples; 46 | Start = start; 47 | End = end; 48 | LastDate = lastDate; 49 | DataCount = dataCount; 50 | } 51 | 52 | public bool IsFull => Samples.Count >= MaxSamplesCount; 53 | public bool IsEmpty => Samples.Count == 0; 54 | public bool IsOverflow => Samples.Count > MaxSamplesCount; 55 | 56 | public abstract void Resample(TimeSpan sampleInterval, int samplesCount); 57 | 58 | public static TSelf Empty(TimeSpan? sampleInterval = null, int maxSamplesCount = 0) 59 | { 60 | return (TSelf) Activator.CreateInstance(typeof(TSelf), sampleInterval ?? TimeSpan.Zero, maxSamplesCount)!; 61 | } 62 | 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | public abstract void Merge(TSelf accumulator); 65 | 66 | public void AddNewData(T data) 67 | { 68 | var rounded = DateTimeOffset.UtcNow.Round(SampleInterval); 69 | lock (_sync) 70 | { 71 | DataCount += 1; 72 | AddData(rounded, data); 73 | End = rounded; 74 | LastDate = DateTimeOffset.UtcNow; 75 | CheckSamplesSize(); 76 | } 77 | } 78 | 79 | public void AddNewData(DateTimeOffset dateTimeOffset, T data) 80 | { 81 | lock (_sync) 82 | { 83 | DataCount += 1; 84 | AddData(dateTimeOffset.Round(SampleInterval), data); 85 | CheckSamplesSize(); 86 | } 87 | } 88 | 89 | public void MarkupAllSamples(MarkupDirection direction = MarkupDirection.Past) 90 | { 91 | var samples = MaxSamplesCount > 0 ? MaxSamplesCount : DefaultSampleCount; 92 | 93 | if (direction is MarkupDirection.Past or MarkupDirection.Feature) 94 | { 95 | var now = Start; 96 | for (var i = 0; i < samples; i++) 97 | { 98 | now = now.Round(SampleInterval); 99 | if (!Samples.ContainsKey(now)) 100 | { 101 | Samples.Add(now, Activator.CreateInstance()); 102 | } 103 | 104 | now = direction is MarkupDirection.Feature ? now.Add(SampleInterval) : now.Subtract(SampleInterval); 105 | } 106 | } 107 | else 108 | { 109 | var nowForFeature = Start; 110 | var nowForPast = Start; 111 | 112 | for (var i = 0; i < samples / 2 + 1; i++) 113 | { 114 | nowForFeature = nowForFeature.Round(SampleInterval); 115 | nowForPast = nowForPast.Round(SampleInterval); 116 | 117 | if (!Samples.ContainsKey(nowForFeature)) 118 | { 119 | Samples.Add(nowForFeature, Activator.CreateInstance()); 120 | } 121 | 122 | if (!Samples.ContainsKey(nowForPast)) 123 | { 124 | Samples.Add(nowForPast, Activator.CreateInstance()); 125 | } 126 | 127 | nowForFeature = nowForFeature.Add(SampleInterval); 128 | nowForPast = nowForPast.Subtract(SampleInterval); 129 | } 130 | } 131 | } 132 | 133 | public void DeleteOverdueSamples() 134 | { 135 | var dateTime = DateTimeOffset.UtcNow.Round(SampleInterval); 136 | for (int i = 0; i < MaxSamplesCount; i++) 137 | { 138 | dateTime = dateTime.Subtract(SampleInterval); 139 | } 140 | 141 | lock (_sync) 142 | { 143 | foreach (var date in Samples.Keys.ToArray()) 144 | { 145 | if (date < dateTime) 146 | { 147 | Samples.Remove(date); 148 | } 149 | } 150 | } 151 | } 152 | 153 | public TSelf Rebase(IEnumerable accumulators) 154 | { 155 | var empty = (TSelf) Activator.CreateInstance(typeof(TSelf), SampleInterval, MaxSamplesCount)!; 156 | 157 | foreach (var accumulator in accumulators) 158 | { 159 | Merge(accumulator); 160 | } 161 | 162 | return empty; 163 | } 164 | 165 | public void Merge(IEnumerable accumulators) 166 | { 167 | foreach (var accumulator in accumulators) 168 | { 169 | Merge(accumulator); 170 | } 171 | } 172 | 173 | public TSelf Rebase(TSelf accumulator) 174 | { 175 | var empty = (TSelf) Activator.CreateInstance(typeof(TSelf), SampleInterval, MaxSamplesCount)!; 176 | empty.Merge(accumulator); 177 | 178 | return empty; 179 | } 180 | 181 | public static TSelf operator +(BaseTimeSeries left, TSelf right) 182 | { 183 | return left.Rebase(right); 184 | } 185 | 186 | public static TSelf operator checked +(BaseTimeSeries left, TSelf right) 187 | { 188 | return left.Rebase(right); 189 | } 190 | 191 | 192 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 193 | protected abstract void AddData(DateTimeOffset now, T data); 194 | 195 | protected void CheckSamplesSize() 196 | { 197 | lock (_sync) 198 | { 199 | if (MaxSamplesCount <= 0) 200 | { 201 | return; 202 | } 203 | 204 | while (IsOverflow) 205 | { 206 | Samples.Remove(Samples.Keys.MinBy(o => o)); //check performance here 207 | } 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/BaseTimeSeriesAccumulator.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Extensions; 2 | 3 | namespace ManagedCode.TimeSeries.Abstractions; 4 | 5 | public abstract class BaseTimeSeriesAccumulator : BaseTimeSeries, TSelf> where TSelf : BaseTimeSeries, TSelf> 6 | { 7 | protected BaseTimeSeriesAccumulator(TimeSpan sampleInterval, int maxSamplesCount) : base(sampleInterval, maxSamplesCount) 8 | { 9 | } 10 | 11 | protected BaseTimeSeriesAccumulator(TimeSpan sampleInterval, int maxSamplesCount, DateTimeOffset start, DateTimeOffset end, 12 | DateTimeOffset lastDate) 13 | : base(sampleInterval, maxSamplesCount, start, end, lastDate) 14 | { 15 | } 16 | 17 | protected override void AddData(DateTimeOffset date, T data) 18 | { 19 | if (!Samples.ContainsKey(date)) 20 | { 21 | Samples.Add(date, new Queue()); 22 | } 23 | 24 | Samples[date].Enqueue(data); 25 | } 26 | 27 | public void Trim() 28 | { 29 | TrimStart(); 30 | TrimEnd(); 31 | } 32 | 33 | public void TrimStart() 34 | { 35 | foreach (var item in Samples.ToArray()) 36 | { 37 | if (item.Value.Count > 0) 38 | { 39 | break; 40 | } 41 | 42 | Samples.Remove(item.Key); 43 | } 44 | } 45 | 46 | public void TrimEnd() 47 | { 48 | foreach (var item in Samples.Reverse().ToArray()) 49 | { 50 | if (item.Value.Count > 0) 51 | { 52 | break; 53 | } 54 | 55 | Samples.Remove(item.Key); 56 | } 57 | } 58 | 59 | public override void Merge(TSelf accumulator) 60 | { 61 | DataCount += accumulator.DataCount; 62 | LastDate = accumulator.LastDate > LastDate ? accumulator.LastDate : LastDate; 63 | foreach (var sample in accumulator.Samples.ToArray()) 64 | { 65 | if (Samples.TryGetValue(sample.Key, out var queue)) 66 | { 67 | foreach (var q in sample.Value.ToArray()) 68 | { 69 | queue.Enqueue(q); 70 | } 71 | } 72 | else 73 | { 74 | Samples.Add(sample.Key, sample.Value); 75 | } 76 | } 77 | 78 | CheckSamplesSize(); 79 | } 80 | 81 | public override void Resample(TimeSpan sampleInterval, int samplesCount) 82 | { 83 | if (sampleInterval <= SampleInterval) 84 | { 85 | throw new InvalidOperationException(); 86 | } 87 | 88 | SampleInterval = sampleInterval; 89 | MaxSamplesCount = samplesCount; 90 | 91 | var samples = Samples; 92 | 93 | Samples = new Dictionary>(); 94 | 95 | foreach (var (key, value) in samples) 96 | { 97 | foreach (var v in value) 98 | { 99 | AddNewData(key, v); 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/BaseTimeSeriesByValueAccumulator.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.TimeSeries.Abstractions; 2 | 3 | public abstract class BaseTimeSeriesByValueAccumulator : BaseTimeSeries, TSelf> 4 | where TSelf : BaseTimeSeries, TSelf> 5 | { 6 | protected BaseTimeSeriesByValueAccumulator(TimeSpan sampleInterval, int samplesCount) : base(sampleInterval, samplesCount) 7 | { 8 | } 9 | 10 | protected BaseTimeSeriesByValueAccumulator(TimeSpan sampleInterval, int samplesCount, DateTimeOffset start, DateTimeOffset end, 11 | DateTimeOffset lastDate) 12 | : base(sampleInterval, samplesCount, start, end, lastDate) 13 | { 14 | } 15 | 16 | protected override void AddData(DateTimeOffset date, T data) 17 | { 18 | if (!Samples.ContainsKey(date)) 19 | { 20 | Samples.Add(date, new HashSet()); 21 | } 22 | 23 | Samples[date].Add(data); 24 | } 25 | 26 | // public BaseTimeSeriesByValueAccumulator Trim() 27 | // { 28 | // TrimStart(); 29 | // TrimEnd(); 30 | // return this; 31 | // } 32 | // 33 | // public BaseTimeSeriesByValueAccumulator TrimStart() 34 | // { 35 | // foreach (var item in Samples.ToArray()) 36 | // { 37 | // if (item.Value.Count > 0) 38 | // { 39 | // break; 40 | // } 41 | // 42 | // Samples.Remove(item.Key); 43 | // } 44 | // 45 | // return this; 46 | // } 47 | // 48 | // public BaseTimeSeriesByValueAccumulator TrimEnd() 49 | // { 50 | // foreach (var item in Samples.Reverse().ToArray()) 51 | // { 52 | // if (item.Value.Count > 0) 53 | // { 54 | // break; 55 | // } 56 | // 57 | // Samples.Remove(item.Key); 58 | // } 59 | // 60 | // return this; 61 | // } 62 | 63 | public override void Merge(TSelf accumulator) 64 | { 65 | DataCount += accumulator.DataCount; 66 | LastDate = accumulator.LastDate > LastDate ? accumulator.LastDate : LastDate; 67 | foreach (var sample in accumulator.Samples.ToArray()) 68 | { 69 | if (Samples.TryGetValue(sample.Key, out var hashSet)) 70 | { 71 | foreach (var v in sample.Value.ToArray()) 72 | { 73 | hashSet.Add(v); 74 | } 75 | } 76 | else 77 | { 78 | Samples.Add(sample.Key, sample.Value); 79 | } 80 | } 81 | 82 | CheckSamplesSize(); 83 | } 84 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/BaseTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace ManagedCode.TimeSeries.Abstractions; 4 | 5 | public abstract class BaseTimeSeriesSummer : BaseTimeSeries 6 | where TSummerItem : ISummerItem where TSelf : BaseTimeSeries 7 | { 8 | private readonly Strategy _strategy; 9 | 10 | protected BaseTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount, Strategy strategy) : base(sampleInterval, maxSamplesCount) 11 | { 12 | _strategy = strategy; 13 | } 14 | 15 | protected override void AddData(DateTimeOffset date, TSummerItem data) 16 | { 17 | if (!Samples.ContainsKey(date)) 18 | { 19 | Samples.Add(date, TSummerItem.Zero); 20 | } 21 | 22 | Samples[date] = Update(Samples[date], data); 23 | } 24 | 25 | public override void Merge(TSelf accumulator) 26 | { 27 | DataCount += accumulator.DataCount; 28 | foreach (var sample in accumulator.Samples.ToArray()) 29 | { 30 | if (Samples.ContainsKey(sample.Key)) 31 | { 32 | Samples[sample.Key] = Update(Samples[sample.Key], sample.Value); 33 | } 34 | else 35 | { 36 | Samples.Add(sample.Key, sample.Value); 37 | } 38 | } 39 | 40 | CheckSamplesSize(); 41 | } 42 | 43 | public override void Resample(TimeSpan sampleInterval, int samplesCount) 44 | { 45 | if (sampleInterval <= SampleInterval) 46 | { 47 | throw new InvalidOperationException(); 48 | } 49 | 50 | SampleInterval = sampleInterval; 51 | MaxSamplesCount = samplesCount; 52 | 53 | var samples = Samples; 54 | 55 | Samples = new Dictionary(); 56 | 57 | foreach (var (key, value) in samples) 58 | { 59 | AddNewData(key, value); 60 | } 61 | } 62 | 63 | private TSummerItem Update(TSummerItem left, TSummerItem right) 64 | { 65 | return _strategy switch 66 | { 67 | Strategy.Sum => left + right, 68 | Strategy.Min => TSummerItem.Min(left, right), 69 | Strategy.Max => TSummerItem.Max(left, right), 70 | Strategy.Replace => right, 71 | _ => throw new ArgumentOutOfRangeException() 72 | }; 73 | } 74 | 75 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 | public virtual void Increment() 77 | { 78 | AddNewData(TSummerItem.One); 79 | } 80 | 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 82 | public virtual void Decrement() 83 | { 84 | AddNewData(-TSummerItem.One); 85 | } 86 | 87 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 88 | public virtual TSummerItem? Min() 89 | { 90 | lock (Samples) 91 | { 92 | return Samples.Values.Min(); 93 | } 94 | } 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public virtual TSummerItem? Max() 98 | { 99 | lock (Samples) 100 | { 101 | return Samples.Values.Max(); 102 | } 103 | } 104 | 105 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 | public virtual TSummerItem Sum() 107 | { 108 | lock (Samples) 109 | { 110 | return Samples.Aggregate(TSummerItem.Zero, (current, sample) => current + sample.Value); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/ISummerItem.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace ManagedCode.TimeSeries.Abstractions; 4 | 5 | public interface ISummerItem : 6 | IUnaryNegationOperators, 7 | IAdditionOperators 8 | where TSelf : ISummerItem 9 | { 10 | /// Gets the radix, or base, for the type. 11 | static abstract TSelf Zero { get; } 12 | 13 | /// Gets the value 1 for the type. 14 | static abstract TSelf One { get; } 15 | 16 | /// Compares two values to compute which is lesser. 17 | /// The value to compare with . 18 | /// The value to compare with . 19 | /// if it is less than ; otherwise, . 20 | static abstract TSelf Min(TSelf x, TSelf y); 21 | 22 | /// Compares two values to compute which is greater. 23 | /// The value to compare with . 24 | /// The value to compare with . 25 | /// if it is greater than ; otherwise, . 26 | static abstract TSelf Max(TSelf x, TSelf y); 27 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/ITimeSeries.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.Contracts; 2 | 3 | namespace ManagedCode.TimeSeries.Abstractions; 4 | 5 | public interface ITimeSeries where TSelf : ITimeSeries 6 | { 7 | void AddNewData(T data); 8 | void AddNewData(DateTimeOffset dateTimeOffset, T data); 9 | 10 | void DeleteOverdueSamples(); 11 | void MarkupAllSamples(MarkupDirection direction = MarkupDirection.Past); 12 | 13 | [Pure] 14 | TSelf Rebase(TSelf accumulator); 15 | 16 | [Pure] 17 | TSelf Rebase(IEnumerable accumulators); 18 | 19 | void Merge(TSelf accumulator); 20 | void Merge(IEnumerable accumulators); 21 | 22 | void Resample(TimeSpan sampleInterval, int samplesCount); 23 | 24 | [Pure] 25 | static abstract TSelf Empty(TimeSpan? sampleInterval = null, int maxSamplesCount = 0); 26 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Abstractions/Strategy.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.TimeSeries.Abstractions; 2 | 3 | public enum Strategy 4 | { 5 | Sum, 6 | Min, 7 | Max, 8 | Replace, 9 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Accumulators/DoubleTimeSeriesAccumulator.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Accumulators; 4 | 5 | public class DoubleTimeSeriesAccumulator : BaseTimeSeriesAccumulator 6 | { 7 | public DoubleTimeSeriesAccumulator(TimeSpan sampleInterval, int maxSamplesCount = 0) : base(sampleInterval, maxSamplesCount) 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Accumulators/FloatTimeSeriesAccumulator.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Accumulators; 4 | 5 | public class FloatTimeSeriesAccumulator : BaseTimeSeriesAccumulator 6 | { 7 | public FloatTimeSeriesAccumulator(TimeSpan sampleInterval, int maxSamplesCount = 0) : base(sampleInterval, maxSamplesCount) 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Accumulators/IntTimeSeriesAccumulator.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Accumulators; 4 | 5 | public class IntTimeSeriesAccumulator : BaseTimeSeriesAccumulator 6 | { 7 | public IntTimeSeriesAccumulator(TimeSpan sampleInterval, int maxSamplesCount = 0) : base(sampleInterval, maxSamplesCount) 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Accumulators/StringTimeSeriesAccumulator.cs: -------------------------------------------------------------------------------- 1 | // namespace ManagedCode.TimeSeries.Accumulators; 2 | // 3 | // public class StringTimeSeriesAccumulator : BaseTimeSeriesByValueAccumulator 4 | // { 5 | // public StringTimeSeriesAccumulator(TimeSpan sampleInterval, int samplesCount = 0) : base(sampleInterval, samplesCount) 6 | // { 7 | // } 8 | // } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Extensions/RoundDateTimeAndTimeSpanExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.TimeSeries.Extensions; 2 | 3 | public static class RoundDateTimeAndTimeSpanExtensions 4 | { 5 | public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval, MidpointRounding roundingType) 6 | { 7 | return new TimeSpan(Convert.ToInt64(Math.Round(time.Ticks / (decimal)roundingInterval.Ticks, roundingType)) * roundingInterval.Ticks); 8 | } 9 | 10 | public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval) 11 | { 12 | return Round(time, roundingInterval, MidpointRounding.ToEven); 13 | } 14 | 15 | public static DateTime Round(this DateTime datetime, TimeSpan roundingInterval) 16 | { 17 | return new DateTime((datetime - DateTime.MinValue).Round(roundingInterval).Ticks); 18 | } 19 | 20 | public static DateTimeOffset Round(this DateTimeOffset dateTimeOffset, TimeSpan roundingInterval) 21 | { 22 | var datetime = dateTimeOffset.UtcDateTime.Round(roundingInterval); 23 | 24 | return new DateTimeOffset(datetime, dateTimeOffset.Offset); 25 | } 26 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/ManagedCode.TimeSeries.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 11 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | true 17 | GeneratedCodeAttribute 18 | [*]*.Migrations.* 19 | **/MyFile.cs 20 | lcov 21 | 22 | 23 | 24 | 25 | ManagedCode.TimeSeries 26 | ManagedCode.TimeSeries 27 | TimeSeries 28 | managedcode, TimeSeries 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/MarkupDirection.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.TimeSeries; 2 | 3 | public enum MarkupDirection 4 | { 5 | Middle, 6 | Past, 7 | Feature 8 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Summers/DoubleGroupTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Summers; 4 | 5 | public class DoubleGroupTimeSeriesSummer : BaseGroupNumberTimeSeriesSummer 6 | { 7 | private readonly TimeSpan _sampleInterval; 8 | private readonly int _samplesCount; 9 | private readonly Strategy _strategy; 10 | 11 | public DoubleGroupTimeSeriesSummer(TimeSpan sampleInterval, int samplesCount, Strategy strategy, bool deleteOverdueSamples) : base(sampleInterval, 12 | deleteOverdueSamples) 13 | { 14 | _sampleInterval = sampleInterval; 15 | _samplesCount = samplesCount; 16 | _strategy = strategy; 17 | } 18 | 19 | public override double Average() 20 | { 21 | lock (TimeSeries) 22 | { 23 | if (TimeSeries.Count == 0) 24 | return 0; 25 | 26 | return TimeSeries.Select(s => s.Value.Average()).Average(); 27 | } 28 | } 29 | 30 | public override double Min() 31 | { 32 | lock (TimeSeries) 33 | { 34 | if (TimeSeries.Count == 0) 35 | return 0; 36 | 37 | return TimeSeries.Select(s => s.Value.Min()).Min(); 38 | } 39 | } 40 | 41 | public override double Max() 42 | { 43 | lock (TimeSeries) 44 | { 45 | if (TimeSeries.Count == 0) 46 | return 0; 47 | 48 | return TimeSeries.Select(s => s.Value.Max()).Max(); 49 | } 50 | } 51 | 52 | public override double Sum() 53 | { 54 | lock (TimeSeries) 55 | { 56 | if (TimeSeries.Count == 0) 57 | return 0; 58 | 59 | return TimeSeries.Select(s => s.Value.Sum()).Sum(); 60 | } 61 | } 62 | 63 | protected override DoubleTimeSeriesSummer CreateSummer() 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Summers/DoubleTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Summers; 4 | 5 | public class DoubleTimeSeriesSummer : BaseNumberTimeSeriesSummer 6 | { 7 | public DoubleTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount, Strategy strategy) : base(sampleInterval, maxSamplesCount, strategy) 8 | { 9 | } 10 | 11 | public DoubleTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount) : base(sampleInterval, maxSamplesCount, Strategy.Sum) 12 | { 13 | } 14 | 15 | public DoubleTimeSeriesSummer(TimeSpan sampleInterval) : base(sampleInterval, 0, Strategy.Sum) 16 | { 17 | } 18 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Summers/FloatGroupNumberTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Summers; 4 | 5 | public class FloatGroupNumberTimeSeriesSummer : BaseGroupNumberTimeSeriesSummer 6 | { 7 | private readonly TimeSpan _sampleInterval; 8 | private readonly int _samplesCount; 9 | private readonly Strategy _strategy; 10 | 11 | public FloatGroupNumberTimeSeriesSummer(TimeSpan sampleInterval, int samplesCount, Strategy strategy, bool deleteOverdueSamples) : base(sampleInterval, 12 | deleteOverdueSamples) 13 | { 14 | _sampleInterval = sampleInterval; 15 | _samplesCount = samplesCount; 16 | _strategy = strategy; 17 | } 18 | 19 | public override float Average() 20 | { 21 | lock (TimeSeries) 22 | { 23 | if (TimeSeries.Count == 0) 24 | return 0; 25 | 26 | return TimeSeries.Select(s => s.Value.Average()).Average(); 27 | } 28 | } 29 | 30 | public override float Min() 31 | { 32 | lock (TimeSeries) 33 | { 34 | if (TimeSeries.Count == 0) 35 | return 0; 36 | 37 | return TimeSeries.Select(s => s.Value.Min()).Min(); 38 | } 39 | } 40 | 41 | public override float Max() 42 | { 43 | lock (TimeSeries) 44 | { 45 | if (TimeSeries.Count == 0) 46 | return 0; 47 | 48 | return TimeSeries.Select(s => s.Value.Max()).Max(); 49 | } 50 | } 51 | 52 | public override float Sum() 53 | { 54 | lock (TimeSeries) 55 | { 56 | if (TimeSeries.Count == 0) 57 | return 0; 58 | 59 | return TimeSeries.Select(s => s.Value.Sum()).Sum(); 60 | } 61 | } 62 | 63 | protected override FloatTimeSeriesSummer CreateSummer() 64 | { 65 | return new FloatTimeSeriesSummer(_sampleInterval, _samplesCount, _strategy); 66 | } 67 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Summers/FloatTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Summers; 4 | 5 | public class FloatTimeSeriesSummer : BaseNumberTimeSeriesSummer 6 | { 7 | public FloatTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount, Strategy strategy) : base(sampleInterval, maxSamplesCount, strategy) 8 | { 9 | } 10 | 11 | public FloatTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount) : base(sampleInterval, maxSamplesCount, Strategy.Sum) 12 | { 13 | } 14 | 15 | public FloatTimeSeriesSummer(TimeSpan sampleInterval) : base(sampleInterval, 0, Strategy.Sum) 16 | { 17 | } 18 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Summers/IntGroupNumberTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Summers; 4 | 5 | public class IntGroupNumberTimeSeriesSummer : BaseGroupNumberTimeSeriesSummer 6 | { 7 | private readonly TimeSpan _sampleInterval; 8 | private readonly int _samplesCount; 9 | private readonly Strategy _strategy; 10 | 11 | public IntGroupNumberTimeSeriesSummer(TimeSpan sampleInterval, int samplesCount, Strategy strategy, bool deleteOverdueSamples) : base(sampleInterval, deleteOverdueSamples) 12 | { 13 | _sampleInterval = sampleInterval; 14 | _samplesCount = samplesCount; 15 | _strategy = strategy; 16 | } 17 | 18 | public override int Average() 19 | { 20 | lock (TimeSeries) 21 | { 22 | if (TimeSeries.Count == 0) 23 | return 0; 24 | 25 | return Convert.ToInt32(Math.Round(TimeSeries.Select(s => s.Value.Average()).Average())); 26 | } 27 | } 28 | 29 | public override int Min() 30 | { 31 | lock (TimeSeries) 32 | { 33 | if (TimeSeries.Count == 0) 34 | return 0; 35 | 36 | return TimeSeries.Select(s => s.Value.Min()).Min(); 37 | } 38 | } 39 | 40 | public override int Max() 41 | { 42 | lock (TimeSeries) 43 | { 44 | if (TimeSeries.Count == 0) 45 | return 0; 46 | 47 | return TimeSeries.Select(s => s.Value.Max()).Max(); 48 | } 49 | } 50 | 51 | public override int Sum() 52 | { 53 | lock (TimeSeries) 54 | { 55 | if (TimeSeries.Count == 0) 56 | return 0; 57 | 58 | return TimeSeries.Select(s => s.Value.Sum()).Sum(); 59 | } 60 | } 61 | 62 | protected override IntTimeSeriesSummer CreateSummer() 63 | { 64 | return new IntTimeSeriesSummer(_sampleInterval, _samplesCount, _strategy); 65 | } 66 | } -------------------------------------------------------------------------------- /ManagedCode.TimeSeries/Summers/IntTimeSeriesSummer.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.TimeSeries.Abstractions; 2 | 3 | namespace ManagedCode.TimeSeries.Summers; 4 | 5 | public class IntTimeSeriesSummer : BaseNumberTimeSeriesSummer 6 | { 7 | public IntTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount, Strategy strategy) : base(sampleInterval, maxSamplesCount, strategy) 8 | { 9 | } 10 | 11 | public IntTimeSeriesSummer(TimeSpan sampleInterval, int maxSamplesCount) : base(sampleInterval, maxSamplesCount, Strategy.Sum) 12 | { 13 | } 14 | 15 | public IntTimeSeriesSummer(TimeSpan sampleInterval) : base(sampleInterval, 0, Strategy.Sum) 16 | { 17 | } 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![img|300x200](https://raw.githubusercontent.com/managedcode/TimeSeries/main/logo.png) 2 | 3 | # TimeSeries 4 | 5 | [![.NET](https://github.com/managedcode/TimeSeries/actions/workflows/dotnet.yml/badge.svg)](https://github.com/managedcode/TimeSeries/actions/workflows/dotnet.yml) 6 | [![Coverage Status](https://coveralls.io/repos/github/managedcode/TimeSeries/badge.svg?branch=main&service=github)](https://coveralls.io/github/managedcode/TimeSeries?branch=main) 7 | [![nuget](https://github.com/managedcode/TimeSeries/actions/workflows/nuget.yml/badge.svg?branch=main)](https://github.com/managedcode/TimeSeries/actions/workflows/nuget.yml) 8 | [![CodeQL](https://github.com/managedcode/TimeSeries/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/managedcode/TimeSeries/actions/workflows/codeql-analysis.yml) 9 | 10 | | Version | Package | Description | 11 | | ------- |-------------------------------------------------------------------------------------------------------------------------------------|-----------------| 12 | |[![NuGet Package](https://img.shields.io/nuget/v/ManagedCode.TimeSeries.svg)](https://www.nuget.org/packages/ManagedCode.TimeSeries) | [ManagedCode.TimeSeries](https://www.nuget.org/packages/ManagedCode.TimeSeries) | Core | 13 | 14 | --- 15 | 16 | ## Motivation 17 | Time series data is a common type of data in many applications, such as finance, physics, and engineering. 18 | It is often necessary to store and manipulate large amounts of time series data efficiently in order to perform analysis and make predictions. 19 | 20 | Our C# library, TimeSeries, provides convenient tools for working with time series data in C#. 21 | It includes classes for accumulating and summarizing data in time frames, as well as storing and compressing the data efficiently. This makes it easy to add and manage time series data in your C# projects. 22 | 23 | ## Features 24 | - Accumulators for adding data to time frames. 25 | - Summers for summarizing data in time frames. 26 | - Efficient storage and compression of time series data. 27 | 28 | ## Example 29 | Here's an example of how you might use the TimeSeries library to accumulate and summarize data in a time frame: 30 | 31 | ```csharp 32 | using ManagedCode.TimeSeries; 33 | 34 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(5)); // step 35 | for (int i = 0; i < count; i++) 36 | { 37 | series.AddNewData(i); 38 | } 39 | ``` 40 | 41 | ```csharp 42 | using ManagedCode.TimeSeries; 43 | 44 | var series = new IntTimeSeriesAccumulator(TimeSpan.FromSeconds(0.1)); 45 | for (var i = 0; i < 1000; i++) 46 | { 47 | await Task.Delay(new Random().Next(1, 5)); 48 | series.AddNewData(i); 49 | } 50 | 51 | series.DataCount; // 1000 52 | ``` 53 | 54 | ## Installation 55 | To install the TimeSeries library, you can use NuGet: 56 | 57 | ```bash 58 | dotnet add package ManagedCode.TimeSeries 59 | ``` 60 | 61 | 62 | Conclusion 63 | In summary, the TimeSeries library provides convenient tools for working with time series data in C#. 64 | Its accumulators and summers make it easy to add and summarize data in time frames, and its efficient storage and compression capabilities ensure. -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcode/TimeSeries/8ab0054c9dfa1094143abdc49d006609fe5fd4fd/logo.png --------------------------------------------------------------------------------