Compare commits
40 Commits
32b5a0a6d0
...
main
Author | SHA1 | Date | |
---|---|---|---|
d7ca87d9e5 | |||
f5bb59f433 | |||
09745960cc | |||
ae3023d688 | |||
6f4cd93528 | |||
f9e8345eba | |||
d63589fe73 | |||
28b84eaa77 | |||
27c6369014 | |||
f01dd6fd19 | |||
8be1ee8d40 | |||
b09c3c50f2 | |||
775d7ceb6f | |||
0d4bf28092 | |||
b8df6b773c | |||
4fd838f401 | |||
26a7c9d733 | |||
bec17dae38 | |||
0c5b36fb61 | |||
29cb7105af | |||
ccc298f328 | |||
968ef8e454 | |||
403d2eb03d | |||
f63962516f | |||
216d9ec624 | |||
a4d9689e2d | |||
4f44cf5c0f | |||
686a272064 | |||
c37719b88f | |||
b57abd5738 | |||
89ca38cd85 | |||
b485d2ada4 | |||
be79e142f1 | |||
c6b98ac9c6 | |||
ba97edff35 | |||
374b1bcca5 | |||
ef6d8cf903 | |||
fda9b5f2db | |||
0bb5db4e98 | |||
a74f6093ec |
55
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Build LaTeX Document
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
TEX_NAME: Tesi
|
||||
PDF_NAME: Tesi.pdf
|
||||
|
||||
jobs:
|
||||
build_latex:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Git repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Install MiKTeX
|
||||
run: |
|
||||
curl -fsSL https://miktex.org/download/key | tee /usr/share/keyrings/miktex-keyring.asc > /dev/null
|
||||
echo "deb [signed-by=/usr/share/keyrings/miktex-keyring.asc] https://miktex.org/download/ubuntu jammy universe" | sudo tee /etc/apt/sources.list.d/miktex.list
|
||||
apt-get update
|
||||
apt-get dist-upgrade -y
|
||||
apt-get install miktex -y
|
||||
miktexsetup --shared=yes finish
|
||||
|
||||
- name: Update MiKTeX package database
|
||||
run: miktex packages update-package-database && miktex packages update
|
||||
|
||||
- name: Enable automatic package installation
|
||||
run: initexmf --set-config-value=[MPM]AutoInstall=yes
|
||||
|
||||
- name: Build LaTeX document using latexmk
|
||||
run: latexmk -pdf -interaction=nonstopmode -halt-on-error ${{ env.TEX_NAME }}.tex
|
||||
|
||||
- name: Check for PDF artifact
|
||||
run: file ${{ env.PDF_NAME }} || grep -q ' PDF '
|
||||
|
||||
- name: Delete latest release and tag
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
curl -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" -X DELETE https://gitea.kobim.cloud/api/v1/repos/${{ github.repository }}/releases/tags/latest
|
||||
curl -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" -X DELETE https://gitea.kobim.cloud/api/v1/repos/${{ github.repository }}/tags/latest
|
||||
|
||||
- name: Release PDF artifact
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ${{ env.PDF_NAME }}
|
||||
tag_name: latest
|
||||
body: |
|
||||
PDF document built from latest LaTeX source.
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
10
.gitignore
vendored
@@ -28,8 +28,8 @@ Tesi.pdf
|
||||
.vscode
|
||||
|
||||
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
||||
*.bbl
|
||||
*.bcf
|
||||
*.bbl*
|
||||
*.bcf*
|
||||
*.blg
|
||||
*-blx.aux
|
||||
*-blx.bib
|
||||
@@ -296,3 +296,9 @@ __pycache__/
|
||||
test/
|
||||
venv/
|
||||
.venv/
|
||||
|
||||
# Node.js
|
||||
**/node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
yarn-debug.log*
|
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "peertube/statnerd"]
|
||||
path = peertube/statnerd
|
||||
url = ssh://git@ssh.kobim.cloud:50697/kobim/peertube-collector.git
|
466
Biblio.bib
@@ -1,3 +1,42 @@
|
||||
@book{10.5555/1593511,
|
||||
author = {Van Rossum, Guido and Drake, Fred L.},
|
||||
title = {Python 3 Reference Manual},
|
||||
year = {2009},
|
||||
isbn = {1441412697},
|
||||
publisher = {CreateSpace},
|
||||
address = {Scotts Valley, CA},
|
||||
abstract = {PYTHON 3 Reference Manual (Python Documentation MANUAL Part 2).Python is an easy to learn object-oriented programming language, which combines power with clear syntax. It has modules, classes, exceptions, very high level data types, and dynamic typing. Python is free software. It can be used with GNU (GNU/Linux), Unix, Microsoft Windows and many other systems.This is a printed softcover copy of the official Python documentation from the latest Python 3.0 distribution. For each copy sold $1 will be donated to the Python Software Foundation by the publisher.This book is part of a brand new six-part series of Python documentation books. Searching for "Python Documentation Manual" will show all six available books.ABOUT THE AUTHOR: Guido van Rossum, is the inventor of Python. Fred L. Drake, Jr. is the official editor of the Python documentation.}
|
||||
}
|
||||
|
||||
@book{10.5555/2544030,
|
||||
author = {Chodorow, Kristina},
|
||||
title = {MongoDB: The Definitive Guide},
|
||||
year = {2013},
|
||||
isbn = {1449344682},
|
||||
publisher = {O'Reilly Media, Inc.},
|
||||
abstract = {Manage the huMongous amount of data collected through your web application with MongoDb. This authoritative introductionwritten by a core contributor to the projectshows you the many advantages of using document-oriented databases, and demonstrates how this reliable, high-performance system allows for almost infinite horizontal scalability. This updated second edition provides guidance for database developers, advanced configuration for system administrators, and an overview of the concepts and use cases for other people on your project. Ideal for NoSql newcomers and experienced MongoDb users alike, this guide provides numerous real-world schema design examples.Get started with MongoDb core concepts and vocabulary Perform basic write operations at different levels of safety and speed Create complex queries, with options for limiting, skipping, and sorting results Design an application that works well with MongoDb Aggregate data, including counting, finding distinct values, grouping documents, and using MapReduce Gather and interpret statistics about your collections and databases Set up replica sets and automatic failover in MongoDb Use sharding to scale horizontally, and learn how it impacts applications Delve into monitoring, security and authentication, backup/restore, and other administrative tasks}
|
||||
}
|
||||
|
||||
@inproceedings{10617017,
|
||||
author = {Pant, Sakshi and Yadav, Er. Narinder and Milan and Sharma, Monnie and Bedi, Yash and Raturi, Anshuman},
|
||||
booktitle = {2024 International Conference on Knowledge Engineering and Communication Systems (ICKECS)},
|
||||
title = {Web Scraping Using Beautiful Soup},
|
||||
year = {2024},
|
||||
volume = {1},
|
||||
number = {},
|
||||
pages = {1-6},
|
||||
keywords = {Knowledge engineering;Ethics;Communication systems;Data acquisition;Data collection;Information retrieval;Libraries;Web scraping;Beautiful Soup;Python;data acquisition;HTML parsing;case studies;ethical considerations},
|
||||
doi = {10.1109/ICKECS61492.2024.10617017}
|
||||
}
|
||||
|
||||
@misc{acestreamAnnouncementStream,
|
||||
author = {},
|
||||
title = {{A}nnouncement! {A}{C}{E} {S}tream; {N}ew era of {T}{V} and {I}nternet broadcasting --- oldforum.acestream.media},
|
||||
url = {http://oldforum.acestream.media/index.php?topic=1479.0},
|
||||
year = {},
|
||||
note = {[Accessed 28-09-2023]}
|
||||
}
|
||||
|
||||
@misc{activitypubActivityPubRocks,
|
||||
author = {},
|
||||
title = {{A}ctivity{P}ub {R}ocks! --- activitypub.rocks},
|
||||
@@ -6,7 +45,7 @@
|
||||
note = {[Accessed 04-Apr-2023]}
|
||||
}
|
||||
|
||||
@misc{ai4businessDataTroppo,
|
||||
@misc{ai4businessDataTroppo,
|
||||
author = {Federica Maria Rita Livelli},
|
||||
title = {{A}{I} e big data, troppo potere nelle mani di pochi: il dibattito - {A}{I}4{B}usiness --- ai4business.it},
|
||||
howpublished = {\url{https://www.ai4business.it/intelligenza-artificiale/ai-e-big-data-favoriscono-la-concentrazione-del-potere/}},
|
||||
@@ -14,7 +53,23 @@
|
||||
note = {[Accessed 01-Sep-2022]}
|
||||
}
|
||||
|
||||
@misc{ARPANET_2022,
|
||||
@misc{aiknowTimeSeries,
|
||||
author = {Angelo Lazzari},
|
||||
title = {{T}ime series: una piccola, ma dettagliata, introduzione - {A}{I}{K}now --- aiknow.io},
|
||||
howpublished = {\url{https://www.aiknow.io/cosa-sono-le-time-series/?doing_wp_cron=1742414242.9129240512847900390625}},
|
||||
year = {},
|
||||
note = {[Accessed 19-03-2025]}
|
||||
}
|
||||
|
||||
@misc{archiveStream,
|
||||
author = {},
|
||||
title = {{A}ce {S}tream --- web.archive.org},
|
||||
url = {https://web.archive.org/web/20180618052904/http://info.acestream.org/#/about/acestream},
|
||||
year = {},
|
||||
note = {[Accessed 28-09-2023]}
|
||||
}
|
||||
|
||||
@misc{ARPANET_2022,
|
||||
title = {{ARPANET} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
rights = {Creative Commons Attribution-ShareAlike License},
|
||||
url = {https://en.wikipedia.org/w/index.php?title=ARPANET&oldid=1105956998},
|
||||
@@ -23,7 +78,7 @@
|
||||
note = {Page Version ID: 1105956998},
|
||||
journal = {Wikipedia},
|
||||
year = {2022},
|
||||
month = {Aug},
|
||||
month = {8},
|
||||
language = {en}
|
||||
}
|
||||
|
||||
@@ -49,6 +104,29 @@
|
||||
journal = {{IEEE} Communications Surveys {\&} Tutorials}
|
||||
}
|
||||
|
||||
@misc{AzureFunctions,
|
||||
author = {Franco Palermo},
|
||||
title = {{A}zure {F}unctions with {D}ocker --- franco.palermo812},
|
||||
howpublished = {\url{https://medium.com/@franco.palermo812/azure-functions-with-docker-47e22866330}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@article{Blum2021,
|
||||
title = {WebRTC - Realtime Communication for the Open Web Platform: What was once a way to bring audio and video to the web has expanded into more use cases we could ever imagine.},
|
||||
volume = {19},
|
||||
issn = {1542-7749},
|
||||
url = {http://dx.doi.org/10.1145/3454122.3457587},
|
||||
doi = {10.1145/3454122.3457587},
|
||||
number = {1},
|
||||
journal = {Queue},
|
||||
publisher = {Association for Computing Machinery (ACM)},
|
||||
author = {Blum, Niklas and Lachapelle, Serge and Alvestrand, Harald},
|
||||
year = {2021},
|
||||
month = feb,
|
||||
pages = {77–93}
|
||||
}
|
||||
|
||||
@article{Budhkar2019,
|
||||
doi = {10.1007/s12083-019-00755-x},
|
||||
url = {https://doi.org/10.1007/s12083-019-00755-x},
|
||||
@@ -63,6 +141,110 @@
|
||||
journal = {Peer-to-Peer Networking and Applications}
|
||||
}
|
||||
|
||||
@misc{framablogPeerTubeOut,
|
||||
author = {},
|
||||
title = {{P}eer{T}ube v6 is out, and powered by your ideas ! --- framablog.org},
|
||||
howpublished = {\url{https://framablog.org/2023/11/28/peertube-v6-is-out-and-powered-by-your-ideas/\#-and-there-s-always-more-}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{framablogPeerTubeV7,
|
||||
author = {},
|
||||
title = {{P}eer{T}ube v7 : offer a complete makeover to your video platform ! --- framablog.org},
|
||||
howpublished = {\url{https://framablog.org/2024/12/17/peertube-v7-offer-a-complete-makeover-to-your-video-platform/}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{framagitFramasoftPeerTube,
|
||||
author = {},
|
||||
title = {{F}ramasoft / {P}eer{T}ube / {S}elenium stack · {G}it{L}ab --- framagit.org},
|
||||
howpublished = {\url{https://framagit.org/framasoft/peertube/selenium-stack}},
|
||||
year = {},
|
||||
note = {[Accessed 20-03-2025]}
|
||||
}
|
||||
|
||||
@misc{geeksforgeeksAggregationMongoDB,
|
||||
author = {},
|
||||
title = {{A}ggregation in {M}ongo{D}{B} - {G}eeksfor{G}eeks --- geeksforgeeks.org},
|
||||
howpublished = {\url{https://www.geeksforgeeks.org/aggregation-in-mongodb/}},
|
||||
year = {},
|
||||
note = {[Accessed 01-04-2025]}
|
||||
}
|
||||
|
||||
@misc{geeksforgeeksContainerizationUsing,
|
||||
author = {},
|
||||
title = {{C}ontainerization using {D}ocker - {G}eeksfor{G}eeks --- geeksforgeeks.org},
|
||||
howpublished = {\url{https://www.geeksforgeeks.org/containerization-using-docker/}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{geeksforgeeksDockerNetworking,
|
||||
author = {},
|
||||
title = {{D}ocker {N}etworking - {G}eeksfor{G}eeks --- geeksforgeeks.org},
|
||||
howpublished = {\url{https://www.geeksforgeeks.org/basics-of-docker-networking/}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{githubGitHubChocobozzzPeerTube,
|
||||
author = {},
|
||||
title = {{G}it{H}ub - {C}hocobozzz/{P}eer{T}ube: {A}ctivity{P}ub-federated video streaming platform using {P}2{P} directly in your web browser --- github.com},
|
||||
howpublished = {\url{https://github.com/Chocobozzz/PeerTube}},
|
||||
year = {},
|
||||
note = {[Accessed 24-03-2025]}
|
||||
}
|
||||
|
||||
@misc{githubGitHubHetznercloudcli,
|
||||
author = {},
|
||||
title = {{G}it{H}ub - hetznercloud/cli: {A} command-line interface for {H}etzner {C}loud --- github.com},
|
||||
howpublished = {\url{https://github.com/hetznercloud/cli}},
|
||||
year = {},
|
||||
note = {[Accessed 20-03-2025]}
|
||||
}
|
||||
|
||||
@misc{githubGitHubInfluxdatatelegraf,
|
||||
author = {},
|
||||
title = {{G}it{H}ub - influxdata/telegraf: {A}gent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data. --- github.com},
|
||||
howpublished = {\url{https://github.com/influxdata/telegraf}},
|
||||
year = {},
|
||||
note = {[Accessed 19-03-2025]}
|
||||
}
|
||||
|
||||
@misc{githubGitHubWebtorrentbittorrenttracker,
|
||||
author = {},
|
||||
title = {{G}it{H}ub - webtorrent/bittorrent-tracker: {S}imple, robust, {B}it{T}orrent tracker (client \& server) implementation --- github.com},
|
||||
howpublished = {\url{https://github.com/webtorrent/bittorrent-tracker?tab=readme-ov-file}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{githubWebtorrentdocsfaqmdMaster,
|
||||
author = {},
|
||||
title = {webtorrent/docs/faq.md at master · webtorrent/webtorrent --- github.com},
|
||||
howpublished = {\url{https://github.com/webtorrent/webtorrent/blob/master/docs/faq.md\#how-does-webtorrent-work}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{influxdataInfrastructureMonitoring,
|
||||
author = {},
|
||||
title = {{I}nfrastructure {M}onitoring {B}asics with {T}elegraf, {I}nflux{D}{B}, and {G}rafana --- influxdata.com},
|
||||
howpublished = {\url{https://www.influxdata.com/blog/infrastructure-monitoring-basics-telegraf-influxdb-grafana/}},
|
||||
year = {},
|
||||
note = {[Accessed 01-04-2025]}
|
||||
}
|
||||
|
||||
@misc{innovationyourselfDockerDocker,
|
||||
author = {},
|
||||
title = {{D}ocker vs {D}ocker {C}ompose vs {D}ocker {S}warm s docker engine --- innovationyourself.com},
|
||||
howpublished = {\url{https://innovationyourself.com/docker-vs-docker-compose-vs-docker-swarm/}},
|
||||
year = {},
|
||||
note = {[Accessed 01-04-2025]}
|
||||
}
|
||||
|
||||
@misc{ipfsIPFSWhitepaper,
|
||||
doi = {10.48550/ARXIV.1407.3561},
|
||||
url = {https://arxiv.org/abs/1407.3561},
|
||||
@@ -82,6 +264,30 @@
|
||||
note = {[Accessed 04-Apr-2023]}
|
||||
}
|
||||
|
||||
@misc{joinpeertubePeerTubeStress,
|
||||
author = {},
|
||||
title = {{P}eer{T}ube stress tests: resilience lies in your peers! | {J}oin{P}eer{T}ube --- joinpeertube.org},
|
||||
howpublished = {\url{https://joinpeertube.org/news/stress-test-2023}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{knowiBestIntroduction,
|
||||
author = {},
|
||||
title = {{T}he {B}est {I}ntroduction to {M}ongo{D}{B} {Q}uery {L}anguage ({M}{Q}{L}) --- knowi.com},
|
||||
howpublished = {\url{https://www.knowi.com/blog/the-best-introduction-to-mongodb-query-language-mql/}},
|
||||
year = {},
|
||||
note = {[Accessed 19-03-2025]}
|
||||
}
|
||||
|
||||
@misc{mongodbDocumentsMongoDB,
|
||||
author = {},
|
||||
title = {{D}ocuments - {M}ongo{D}{B} {M}anual v8.0 - {M}ongo{D}{B} {D}ocs --- mongodb.com},
|
||||
howpublished = {\url{https://www.mongodb.com/docs/manual/core/document/}},
|
||||
year = {},
|
||||
note = {[Accessed 19-03-2025]}
|
||||
}
|
||||
|
||||
@article{Multi-CDN:TowardsPrivacyinContentDeliveryNetworks,
|
||||
doi = {10.1109/tdsc.2018.2833110},
|
||||
url = {https://doi.org/10.1109/tdsc.2018.2833110},
|
||||
@@ -96,6 +302,29 @@
|
||||
journal = {{IEEE} Transactions on Dependable and Secure Computing}
|
||||
}
|
||||
|
||||
@misc{noauthor_telegraf_nodate,
|
||||
title = {Telegraf documentation},
|
||||
url = {https://docs.influxdata.com/telegraf/v1/},
|
||||
abstract = {Documentation for Telegraf, the plugin-driven server agent of the InfluxData time series platform, used to collect and report metrics. Telegraf supports four categories of plugins – input, output, aggregator, and processor.},
|
||||
urldate = {2025-03-19}
|
||||
}
|
||||
|
||||
@misc{novageMediaLoader,
|
||||
author = {},
|
||||
title = {{P}2{P} {M}edia {L}oader --- novage.com.ua},
|
||||
howpublished = {\url{https://novage.com.ua/p2p-media-loader/technical-overview}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{opentelemetryWhatOpenTelemetry,
|
||||
author = {},
|
||||
title = {{W}hat is {O}pen{T}elemetry? --- opentelemetry.io},
|
||||
howpublished = {\url{https://opentelemetry.io/docs/what-is-opentelemetry/}},
|
||||
year = {},
|
||||
note = {[Accessed 25-03-2025]}
|
||||
}
|
||||
|
||||
@article{Parameswaran2001,
|
||||
doi = {10.1109/2.933501},
|
||||
url = {https://doi.org/10.1109/2.933501},
|
||||
@@ -126,14 +355,35 @@
|
||||
note = {[Accessed 07-Mar-2023]}
|
||||
}
|
||||
|
||||
@misc{Redazione_2020,
|
||||
title = {“Cittadinanza digitale e tecnocivismo. In un mondo digitale la cittadinanza inizia dai bit di Andrea Trentini", Giovanni Biscuolo e Andrea Rossi},
|
||||
url = {https://www.letture.org/cittadinanza-digitale-e-tecnocivismo-andrea-trentini-giovanni-biscuolo-andrea-rossi},
|
||||
abstractnote = {Prof. Andrea Trentini, Lei è autore con Giovanni Biscuolo e Andrea Rossi del libro Cittadinanza digitale e tecnocivismo. In un mondo digitale la cittadinanza inizia dai bit edito da Ledizioni: quali aspetti costituiscono la Cittadinanza Digitale? Risposta breve Ovunque ci sia tecnologia c’è influenza sulla cittadinanza… e vale anche il viceversa. Risposta articolata In una [...]Leggi di più... from “Cittadinanza digitale e tecnocivismo. In un mondo digitale la cittadinanza inizia dai bit” di Andrea Trentini, Giovanni Biscuolo e Andrea Rossi},
|
||||
journal = {Letture.org},
|
||||
year = {2020},
|
||||
month = {Dec},
|
||||
language = {it-IT}
|
||||
@misc{qabrainsSeleniumGrid,
|
||||
author = {},
|
||||
title = {{S}elenium {G}rid 4 - {A}rchitecture and {C}ommunication --- qabrains.com},
|
||||
howpublished = {\url{https://qabrains.com/selenium-grid-4-architecture-and-communication}},
|
||||
year = {},
|
||||
note = {[Accessed 01-04-2025]}
|
||||
}
|
||||
|
||||
@phdthesis{ResearchBasedDataRightsManagementUsingBlockchainOverEthereumNetwork,
|
||||
author = {Naz, Muqaddas and Javaid, Nadeem and Iqbal, Sohail},
|
||||
year = {2019},
|
||||
month = {09},
|
||||
pages = {},
|
||||
title = {Research Based Data Rights Management Using Blockchain Over Ethereum Network}
|
||||
}
|
||||
|
||||
@misc{rfc2663,
|
||||
series = {Request for Comments},
|
||||
number = 2663,
|
||||
howpublished = {RFC 2663},
|
||||
publisher = {RFC Editor},
|
||||
doi = {10.17487/RFC2663},
|
||||
url = {https://www.rfc-editor.org/info/rfc2663},
|
||||
author = {Matt Holdrege and Pyda Srisuresh},
|
||||
title = {{IP Network Address Translator (NAT) Terminology and Considerations}},
|
||||
pagetotal = 30,
|
||||
year = 1999,
|
||||
month = aug,
|
||||
abstract = {This document attempts to describe the operation of NAT devices and the associated considerations in general, and to define the terminology used to identify various flavors of NAT. This memo provides information for the Internet community.}
|
||||
}
|
||||
|
||||
@misc{rfc5128,
|
||||
@@ -151,6 +401,52 @@
|
||||
abstract = {This memo documents the various methods known to be in use by applications to establish direct communication in the presence of Network Address Translators (NATs) at the current time. Although this memo is intended to be mainly descriptive, the Security Considerations section makes some purely advisory recommendations about how to deal with security vulnerabilities the applications could inadvertently create when using the methods described. This memo covers NAT traversal approaches used by both TCP- and UDP-based applications. This memo is not an endorsement of the methods described, but merely an attempt to capture them in a document. This memo provides information for the Internet community.}
|
||||
}
|
||||
|
||||
@misc{rfc5389,
|
||||
series = {Request for Comments},
|
||||
number = 5389,
|
||||
howpublished = {RFC 5389},
|
||||
publisher = {RFC Editor},
|
||||
doi = {10.17487/RFC5389},
|
||||
url = {https://www.rfc-editor.org/info/rfc5389},
|
||||
author = {Philip Matthews and Jonathan Rosenberg and Dan Wing and Rohan Mahy},
|
||||
title = {{Session Traversal Utilities for NAT (STUN)}},
|
||||
pagetotal = 51,
|
||||
year = 2008,
|
||||
month = oct,
|
||||
abstract = {Session Traversal Utilities for NAT (STUN) is a protocol that serves as a tool for other protocols in dealing with Network Address Translator (NAT) traversal. It can be used by an endpoint to determine the IP address and port allocated to it by a NAT. It can also be used to check connectivity between two endpoints, and as a keep-alive protocol to maintain NAT bindings. STUN works with many existing NATs, and does not require any special behavior from them. STUN is not a NAT traversal solution by itself. Rather, it is a tool to be used in the context of a NAT traversal solution. This is an important change from the previous version of this specification (RFC 3489), which presented STUN as a complete solution. This document obsoletes RFC 3489. {[}STANDARDS-TRACK{]}}
|
||||
}
|
||||
|
||||
@misc{rfc8216,
|
||||
series = {Request for Comments},
|
||||
number = 8216,
|
||||
howpublished = {RFC 8216},
|
||||
publisher = {RFC Editor},
|
||||
doi = {10.17487/RFC8216},
|
||||
url = {https://www.rfc-editor.org/info/rfc8216},
|
||||
author = {Roger Pantos and William May},
|
||||
title = {{HTTP Live Streaming}},
|
||||
pagetotal = 60,
|
||||
year = 2017,
|
||||
month = aug,
|
||||
abstract = {This document describes a protocol for transferring unbounded streams of multimedia data. It specifies the data format of the files and the actions to be taken by the server (sender) and the clients (receivers) of the streams. It describes version 7 of this protocol.}
|
||||
}
|
||||
|
||||
@misc{scalerWhichBrowser,
|
||||
author = {Simran Kumari},
|
||||
title = {{W}hich {B}rowser {S}upports {S}elenium ? - {S}caler {T}opics --- scaler.com},
|
||||
howpublished = {\url{https://www.scaler.com/topics/selenium-tutorial/which-browser-supports-selenium/}},
|
||||
year = {},
|
||||
note = {[Accessed 20-03-2025]}
|
||||
}
|
||||
|
||||
@misc{seleniumSeleniumBrowser,
|
||||
author = {},
|
||||
title = {{T}he {S}elenium {B}rowser {A}utomation {P}roject --- selenium.dev},
|
||||
howpublished = {\url{https://www.selenium.dev/documentation/}},
|
||||
year = {},
|
||||
note = {[Accessed 20-03-2025]}
|
||||
}
|
||||
|
||||
@misc{tahoelafsAbouttahoerstTrunkdocs,
|
||||
author = {},
|
||||
title = {About-tahoe; {T}ahoe-{L}{A}{F}{S} --- tahoe-lafs.org},
|
||||
@@ -159,6 +455,38 @@
|
||||
note = {[Accessed 02-Apr-2023]}
|
||||
}
|
||||
|
||||
@misc{tailscaleTraversalWorks,
|
||||
author = {},
|
||||
title = {{H}ow {N}{A}{T} traversal works --- tailscale.com},
|
||||
howpublished = {\url{https://tailscale.com/blog/how-nat-traversal-works}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{TestGrid_2024,
|
||||
title = {Selenium WebDriver – Step-by-Step Tutorial},
|
||||
url = {https://testgrid.io/blog/selenium-webdriver/},
|
||||
journal = {TestGrid},
|
||||
year = {2024},
|
||||
month = {9}
|
||||
}
|
||||
|
||||
@online{The360Degree,
|
||||
author = {The 360 Degree},
|
||||
title = {Own work},
|
||||
year = {2022},
|
||||
url = {https://commons.wikimedia.org/w/index.php?curid=116056511},
|
||||
note = {Licensed under CC BY-SA 4.0}
|
||||
}
|
||||
|
||||
@misc{theoryBitTorrentSpecificationTheoryOrg,
|
||||
author = {},
|
||||
title = {{B}it{T}orrent{S}pecification - {T}heory{O}rg --- wiki.theory.org},
|
||||
howpublished = {\url{https://wiki.theory.org/BitTorrentSpecification}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@inproceedings{theTwitchCase,
|
||||
author = {Deng, Jie and Tyson, Gareth and Cuadrado, Félix and Uhlig, Steve},
|
||||
year = {2017},
|
||||
@@ -195,6 +523,22 @@
|
||||
note = {[Accessed 11-Feb-2023]}
|
||||
}
|
||||
|
||||
@misc{uclvDockerOverview,
|
||||
author = {},
|
||||
title = {{D}ocker overview --- docker-docs.uclv.cu},
|
||||
howpublished = {\url{https://docker-docs.uclv.cu/get-started/overview/}},
|
||||
year = {},
|
||||
note = {[Accessed 01-04-2025]}
|
||||
}
|
||||
|
||||
@online{VignoniCalimo2009,
|
||||
author = {David Vignoni and Calimo},
|
||||
title = {By Gnome-fs-client.svg: David Vignoni, Gnome-fs-server.svg: David Vignoni, derivative work: Calimo},
|
||||
year = {2009},
|
||||
url = {https://commons.wikimedia.org/w/index.php?curid=15782858},
|
||||
note = {Licensed under LGPL}
|
||||
}
|
||||
|
||||
@misc{w3c,
|
||||
author = {W3C},
|
||||
title = {What is the difference between the Web and the Internet?},
|
||||
@@ -203,7 +547,31 @@
|
||||
note = {[Accessed 31-Aug-2022]}
|
||||
}
|
||||
|
||||
@misc{wiki:ActivityPub,
|
||||
@misc{webBuildBackend,
|
||||
author = {},
|
||||
title = {{B}uild the backend services needed for a {W}eb{R}{T}{C} app | {A}rticles | web.dev --- web.dev},
|
||||
howpublished = {\url{https://web.dev/articles/webrtc-infrastructure\#what-is-signaling}},
|
||||
year = {},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
||||
|
||||
@misc{webpackConceptsWebpack,
|
||||
author = {},
|
||||
title = {{C}oncepts | webpack --- webpack.js.org},
|
||||
howpublished = {\url{https://webpack.js.org/concepts/}},
|
||||
year = {},
|
||||
note = {[Accessed 22-03-2025]}
|
||||
}
|
||||
|
||||
@misc{WebRTCDebugging,
|
||||
author = {Vittorio Palmisano},
|
||||
title = {{W}eb{R}{T}{C} debugging with {P}rometheus/{G}rafana --- vpalmisano},
|
||||
howpublished = {\url{https://medium.com/@vpalmisano/webrtc-debugging-with-prometheus-grafana-254b6ac71063}},
|
||||
year = {},
|
||||
note = {[Accessed 20-03-2025]}
|
||||
}
|
||||
|
||||
@misc{wiki:ActivityPub,
|
||||
author = {Wikipedia},
|
||||
title = {{ActivityPub} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2023},
|
||||
@@ -211,7 +579,55 @@
|
||||
note = {[Online; accessed 05-April-2023]}
|
||||
}
|
||||
|
||||
@misc{wikipediaLicenzainformatica,
|
||||
@misc{wiki:Broadcasting_networking,
|
||||
author = {Wikipedia},
|
||||
title = {{Broadcasting (networking)} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2025},
|
||||
howpublished = {\url{http://en.wikipedia.org/w/index.php?title=Broadcasting\%20(networking)&oldid=1238402634}},
|
||||
note = {[Online; accessed 31-March-2025]}
|
||||
}
|
||||
|
||||
@misc{wiki:Content_delivery_network,
|
||||
author = {Wikipedia},
|
||||
title = {{Content delivery network} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2025},
|
||||
howpublished = {\url{http://en.wikipedia.org/w/index.php?title=Content\%20delivery\%20network&oldid=1279155231}},
|
||||
note = {[Online; accessed 31-March-2025]}
|
||||
}
|
||||
|
||||
@misc{wiki:Multicast,
|
||||
author = {Wikipedia},
|
||||
title = {{Multicast} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2025},
|
||||
howpublished = {\url{http://en.wikipedia.org/w/index.php?title=Multicast&oldid=1270123732}},
|
||||
note = {[Online; accessed 31-March-2025]}
|
||||
}
|
||||
|
||||
@misc{wiki:Network_address_translation,
|
||||
author = {Wikipedia},
|
||||
title = {{Network address translation} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2025},
|
||||
howpublished = {\url{http://en.wikipedia.org/w/index.php?title=Network\%20address\%20translation&oldid=1283227709}},
|
||||
note = {[Online; accessed 01-April-2025]}
|
||||
}
|
||||
|
||||
@misc{wiki:PeerTube,
|
||||
author = {Wikipedia},
|
||||
title = {{PeerTube} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2025},
|
||||
howpublished = {\url{http://en.wikipedia.org/w/index.php?title=PeerTube&oldid=1281511347}},
|
||||
note = {[Online; accessed 24-March-2025]}
|
||||
}
|
||||
|
||||
@misc{wiki:Unicast,
|
||||
author = {Wikipedia},
|
||||
title = {{Unicast} --- {W}ikipedia{,} The Free Encyclopedia},
|
||||
year = {2025},
|
||||
howpublished = {\url{http://en.wikipedia.org/w/index.php?title=Unicast&oldid=1222709440}},
|
||||
note = {[Online; accessed 31-March-2025]}
|
||||
}
|
||||
|
||||
@misc{wikipediaLicenzainformatica,
|
||||
author = {Wikipedia},
|
||||
title = {{L}icenza (informatica) - {W}ikipedia --- it.wikipedia.org},
|
||||
howpublished = {\url{https://it.wikipedia.org/wiki/Licenza_(informatica)}},
|
||||
@@ -235,26 +651,10 @@
|
||||
note = {[Accessed 09-Feb-2023]}
|
||||
}
|
||||
|
||||
@phdthesis{ResearchBasedDataRightsManagementUsingBlockchainOverEthereumNetwork,
|
||||
author = {Naz, Muqaddas and Javaid, Nadeem and Iqbal, Sohail},
|
||||
year = {2019},
|
||||
month = {09},
|
||||
pages = {},
|
||||
title = {Research Based Data Rights Management Using Blockchain Over Ethereum Network}
|
||||
}
|
||||
|
||||
@misc{acestreamAnnouncementStream,
|
||||
@misc{wikipediaWebRTCWikipedia,
|
||||
author = {},
|
||||
title = {{A}nnouncement! {A}{C}{E} {S}tream; {N}ew era of {T}{V} and {I}nternet broadcasting --- oldforum.acestream.media},
|
||||
url = {http://oldforum.acestream.media/index.php?topic=1479.0},
|
||||
title = {{W}eb{R}{T}{C} - {W}ikipedia --- en.wikipedia.org},
|
||||
howpublished = {\url{https://en.wikipedia.org/wiki/WebRTC}},
|
||||
year = {},
|
||||
note = {[Accessed 28-09-2023]},
|
||||
}
|
||||
|
||||
@misc{archiveStream,
|
||||
author = {},
|
||||
title = {{A}ce {S}tream --- web.archive.org},
|
||||
url = {https://web.archive.org/web/20180618052904/http://info.acestream.org/#/about/acestream},
|
||||
year = {},
|
||||
note = {[Accessed 28-09-2023]},
|
||||
note = {[Accessed 18-03-2025]}
|
||||
}
|
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Mirko Milovanovic
|
||||
Copyright (c) 2025 Mirko Milovanovic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -1,5 +1,9 @@
|
||||
# Titolo: Streaming decentralizzato di contenuti audiovisivi
|
||||
|
||||
## Release
|
||||
|
||||
[PDF aggiornato all'ultimo commit](https://gitea.kobim.cloud/kobim/Tesi/releases/tag/latest)
|
||||
|
||||
## TODO
|
||||
|
||||
* [ ] Scrivere meglio gli use cases nel dettaglio, facendo delle user stories, concretizzando meglio le situazioni nelle quali gli utenti possono trovarsi
|
||||
|
130
Relazione Stage.tex
Normal file
@@ -0,0 +1,130 @@
|
||||
\documentclass[12pt,a4paper]{article}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[italian]{babel}
|
||||
\usepackage[hyphens]{url}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{geometry}
|
||||
\geometry{a4paper, margin=2.5cm}
|
||||
|
||||
% PDF/A compliance - using either pdfx or direct pdflatex configuration
|
||||
% Ensuring all fonts are embedded
|
||||
\pdfminorversion=4
|
||||
\pdfobjcompresslevel=0
|
||||
\pdfcompresslevel=0
|
||||
|
||||
\usepackage[a-1b]{pdfx}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{datetime}
|
||||
|
||||
\title{{\huge{\bf Streaming decentralizzato di}}\\
|
||||
\vspace{4mm}
|
||||
{\huge{\bf contenuti audiovisivi}}\\}
|
||||
|
||||
\author{Mirko Milovanovic - 870671}
|
||||
\date{21 marzo 2025}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\maketitle
|
||||
|
||||
\section{Contesto iniziale}
|
||||
|
||||
Lo stage è stato svolto nell'ambito della ricerca sulle tecnologie per lo streaming decentralizzato di contenuti audiovisivi, con particolare attenzione alla piattaforma PeerTube. Il contesto di partenza vedeva la crescente problematica della centralizzazione dei servizi di streaming video, con poche grandi piattaforme (come YouTube e Twitch) che dominano il mercato, comportando problemi di scalabilità, costi elevati di infrastruttura, controllo centralizzato dei contenuti e potenziali limitazioni alla libertà di espressione.
|
||||
|
||||
PeerTube si propone come alternativa decentralizzata, utilizzando tecnologie peer-to-peer (P2P) per distribuire il carico di banda tra gli utenti. In particolare, il team di PeerTube aveva pubblicato uno studio che affermava una riduzione del carico sui server fino all'80\% grazie alla tecnologia P2P integrata, ma senza fornire dettagli sufficienti sulla metodologia utilizzata né strumenti specifici per riprodurre i test in modo indipendente.
|
||||
|
||||
\section{Obiettivi del lavoro}
|
||||
|
||||
Gli obiettivi principali dello stage erano:
|
||||
|
||||
\begin{itemize}
|
||||
\item Verificare empiricamente le affermazioni degli sviluppatori di PeerTube riguardo all'efficienza del loro approccio P2P nel contesto dello streaming video
|
||||
\item Creare un sistema di test automatizzato che potesse riprodurre in modo indipendente i test descritti nell'articolo originale di PeerTube
|
||||
\item Raccogliere metriche più dettagliate rispetto a quelle presentate nell'articolo originale
|
||||
\item Analizzare i dati raccolti per valutare le reali prestazioni del sistema P2P di PeerTube
|
||||
\item Contribuire alla comprensione delle tecnologie decentralizzate per lo streaming video
|
||||
\end{itemize}
|
||||
|
||||
\section{Descrizione lavoro svolto}
|
||||
|
||||
Il lavoro è stato articolato nelle seguenti fasi:
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Analisi del panorama tecnologico}: Studio approfondito delle tecnologie di streaming esistenti, sia centralizzate (Twitch, YouTube) che decentralizzate (IPFS, Tahoe-LAFS, PeerTube, Ace Stream), con particolare attenzione ai loro punti di forza e debolezza.
|
||||
|
||||
\item \textbf{Approfondimento dell'architettura di PeerTube}: Analisi dettagliata dell'architettura di PeerTube, dei suoi componenti (Node.js, Angular, PostgreSQL) e delle tecnologie P2P utilizzate (Novage P2P Media Loader, WebRTC).
|
||||
|
||||
\item \textbf{Progettazione del sistema di test}: Creazione di un'architettura di test che potesse simulare utenti reali distribuiti geograficamente, raccogliendo metriche dettagliate sulle prestazioni della rete e del sistema P2P.
|
||||
|
||||
\item \textbf{Implementazione del sistema}: Sviluppo di un sistema basato su Docker, Selenium, Python e WebRTC Internals Exporter per simulare client reali che guardano video su PeerTube e raccolgono dati sulle connessioni P2P.
|
||||
|
||||
\item \textbf{Modifica di WebRTC Internals Exporter}: Adattamento di uno strumento esistente per la raccolta di metriche WebRTC, con modifiche sostanziali per adattarlo alle esigenze specifiche del test.
|
||||
|
||||
\item \textbf{Creazione di un sistema di raccolta dati}: Utilizzo di Telegraf e MongoDB per raccogliere, elaborare e archiviare le metriche raccolte dai test.
|
||||
|
||||
\item \textbf{Automazione del deployment}: Utilizzo degli script CLI di Hetzner Cloud per distribuire automaticamente i test su macchine virtuali in diverse regioni geografiche.
|
||||
|
||||
\item \textbf{Esecuzione dei test}: Riproduzione di due scenari di test principali: live streaming con Normal Latency e live streaming con High Latency.
|
||||
|
||||
\item \textbf{Analisi dei risultati}: Elaborazione dei dati raccolti per verificare le affermazioni di PeerTube riguardo all'efficienza del loro approccio P2P.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Tecnologie coinvolte}
|
||||
|
||||
Durante lo stage sono state utilizzate e approfondite numerose tecnologie:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Docker}: Per creare ambienti isolati e facilmente riproducibili
|
||||
\item \textbf{Python}: Come linguaggio principale per l'automazione e l'analisi dei dati
|
||||
\item \textbf{WebRTC}: Per comprendere il funzionamento delle connessioni peer-to-peer nei browser
|
||||
\item \textbf{Selenium}: Per simulare browser reali che guardano stream video
|
||||
\item \textbf{Telegraf}: Per la raccolta di metriche di sistema e di rete
|
||||
\item \textbf{MongoDB}: Per l'archiviazione strutturata dei dati raccolti
|
||||
\item \textbf{WebPack}: Per la compilazione e la gestione delle dipendenze del frontend
|
||||
\item \textbf{Hetzner Cloud}: Per il deployment distribuito dei test
|
||||
\item \textbf{Beautiful Soup 4}: Per l'estrazione di dati dalle pagine web di PeerTube
|
||||
\item \textbf{Novage P2P Media Loader}: Libreria JavaScript per la distribuzione P2P dei video
|
||||
\end{itemize}
|
||||
|
||||
\section{Competenze e risultati raggiunti}
|
||||
|
||||
\textbf{Risultati rispetto agli obiettivi}:
|
||||
\begin{itemize}
|
||||
\item È stato creato con successo un sistema di test automatizzato per verificare le prestazioni di PeerTube
|
||||
\item Sono state raccolte metriche dettagliate sulle connessioni P2P e sulle prestazioni del sistema
|
||||
\item I dati raccolti hanno confermato in gran parte le affermazioni degli sviluppatori di PeerTube, pur con alcune limitazioni dovute alla scala ridotta dei test (5 client anziché 1000)
|
||||
\item Il sistema sviluppato è disponibile come progetto open source, permettendo ad altri ricercatori di riprodurre e ampliare i test
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Insegnamenti tratti dall'esperienza}:
|
||||
\begin{itemize}
|
||||
\item Importanza dell'automazione per test di sistema complessi
|
||||
\item Valore di un approccio modulare e flessibile nello sviluppo di strumenti di test
|
||||
\item Comprensione approfondita delle dinamiche delle reti P2P e delle sfide nella distribuzione di contenuti video
|
||||
\item Acquisizione di competenze avanzate nell'instrumentazione di browser web per la raccolta di metriche
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Problemi incontrati}:
|
||||
\begin{itemize}
|
||||
\item \textbf{Accesso agli indirizzi IP forniti da WebRTC} (Risolto): Le recenti versioni di Chrome hanno implementato misure di sicurezza che offuscano gli indirizzi IP locali. Questo è stato risolto utilizzando una versione specifica di Chromium con opportuni flag di configurazione.
|
||||
|
||||
\item \textbf{Limitazioni delle API di PeerTube} (Risolto): Mancanza di API specifiche per le metriche dei video. Risolto tramite web scraping con BeautifulSoup.
|
||||
|
||||
\item \textbf{Gestione delle variabili d'ambiente nell'estensione Chrome} (Risolto): Difficoltà nella configurazione dinamica dell'estensione WebRTC. Risolto utilizzando WebPack per la sostituzione delle variabili in fase di compilazione.
|
||||
|
||||
\item \textbf{Scala dei test} (Parzialmente risolto): Non è stato possibile replicare la scala dei test originali (1000 client) a causa di limitazioni di risorse. I test sono stati eseguiti con 5 client, che hanno comunque fornito dati significativi sulle dinamiche P2P.
|
||||
|
||||
\item \textbf{Raccolta dei dati su macchine distribuite} (Risolto): Sfida nel raccogliere dati da macchine geograficamente distribuite. Risolto con un'architettura centralizzata di raccolta dati basata su Telegraf e MongoDB.
|
||||
\end{itemize}
|
||||
|
||||
\section{Bibliografia}
|
||||
|
||||
\begin{itemize}
|
||||
\item RFC 2663: IP Network Address Translator (NAT) Terminology and Considerations
|
||||
\item RFC 8216: HTTP Live Streaming
|
||||
\item W3C WebRTC
|
||||
\item The Selenium Browser Automation Project
|
||||
\item PeerTube
|
||||
\end{itemize}
|
||||
|
||||
\end{document}
|
5
Tesi.xmpdata
Normal file
@@ -0,0 +1,5 @@
|
||||
\Title{Streaming decentralizzato di contenuti audiovisivi}
|
||||
\Author{Mirko Milovanovic}
|
||||
\Language{it-IT}
|
||||
\Subject{Tesi triennale in Informatica per la Comunicazione Digitale - Analisi di soluzioni per lo streaming decentralizzato di contenuti audiovisivi}
|
||||
\Keywords{streaming, decentralizzato, audiovisivo, video, peer-to-peer, WebRTC, IPFS, DTube, BitTorrent, blockchain, Ethereum, smart contract, IPFS, DTube, BitTorrent, blockchain, Ethereum, smart contract, P2P}
|
BIN
images/0_ukWqRD74ltfb5Uaz.png
Normal file
After Width: | Height: | Size: 39 KiB |
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg
|
||||
width="360"
|
||||
height="87.039368"
|
||||
viewBox="0 0 360 87.039367"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g
|
||||
transform="matrix(1.2293722,0,0,1.2293722,29.999995,7.2531291)">
|
||||
<g
|
||||
clip-path="url(#clip0_205_625)">
|
||||
<path
|
||||
d="M 55.4998,2.93744 C 50.6758,-0.0519845 46.0011,-0.485235 41.754,0.421702 37.6819,1.2882 33.7619,3.45734 30.4359,6.01929 29.575,5.46762 28.7428,4.91595 27.9249,4.38161 c -0.7461,1.62613 -1.5123,3.28981 -2.2957,4.97371 -0.7921,1.73298 -1.5975,3.49588 -2.4163,5.28858 2.1781,-0.7048 4.3877,-1.424 6.6462,-2.149 l 7.025,-2.2644 -3.2313,-2.10561 c 2.9759,-2.13448 6.2502,-3.94258 9.757,-4.97082 3.9343,-1.15262 8.103,-1.2274 12.0756,-0.21663 z"
|
||||
fill="#f15e4b"/>
|
||||
<path
|
||||
d="M 63.2681,29.8018 C 67.013,21.7434 66.574,16.4577 63.9912,13.191 61.5893,10.1525 57.3479,8.8383 52.8712,8.59857 53.4997,7.18328 54.1252,5.78533 54.7279,4.41626 l -7.4124,3.466 -7.1743,3.35914 c 1.2397,1.1765 2.5235,2.3954 3.8512,3.6566 l 4.3045,4.0928 c 0.7652,-1.733 1.5142,-3.441 2.247,-5.1239 3.6703,0.5199 7.0766,1.7041 9.4699,4.0263 2.5282,2.4696 3.9344,6.2504 3.2629,11.9289 z"
|
||||
fill="#8a5da7"/>
|
||||
<path
|
||||
d="m 31.2939,59.0029 c 9.8173,-1.8313 16.2913,-6.184 19.9616,-11.4205 1.577,-2.2256 2.683,-4.7535 3.2492,-7.4271 0.5662,-2.6735 0.5806,-5.4357 0.0423,-8.115 2.2527,-0.7539 4.5169,-1.5222 6.8156,-2.3107 -2.5541,-1.398 -5.001,-2.7459 -7.3407,-4.0437 -2.2345,-1.2131 -4.3849,-2.3838 -6.4511,-3.5122 -1.211,2.3473 -2.4469,4.7475 -3.7076,7.2006 -1.2799,2.5032 -2.5866,5.0633 -3.92,7.6801 2.2498,-0.7741 4.5312,-1.5539 6.8471,-2.3627 0.3422,4.4025 -0.5982,8.8099 -2.7061,12.6827 C 41.6908,51.7531 37.6073,55.843 31.2939,59 Z"
|
||||
fill="#4a8da0" />
|
||||
<path
|
||||
d="m 2.24982,47.3167 c 2.53106,5.5283 6.54002,7.914 11.11718,8.0411 4.8211,0.1329 9.998,-2.2413 14.535,-5.6351 1.2167,1.3363 2.4679,2.7121 3.7535,4.1274 0.9815,-2.8739 1.9514,-5.6698 2.8869,-8.3935 0.9202,-2.6342 1.8156,-5.2077 2.6861,-7.7205 -2.9472,0.6701 -5.8341,1.3113 -8.655,1.9496 l -8.1241,1.8399 c 1.1048,1.2015 2.2355,2.4464 3.4063,3.723 -3.7076,2.5389 -7.6075,4.4307 -11.4357,4.942 C 8.72097,50.696 5.21708,49.9018 2.24982,47.3022 Z"
|
||||
fill="#26a75a"/>
|
||||
<path
|
||||
d="M 5.00762,20.8682 C 1.10197,26.6015 -0.258264,30.8849 0.0373139,34.0159 0.35298,37.3288 2.54542,39.3795 5.5586,40.4107 c -0.90683,1.7128 -1.83947,3.4458 -2.78073,5.199 2.28427,-1.034 4.59628,-2.0844 6.93604,-3.1512 2.36459,-1.06 4.75219,-2.1518 7.17419,-3.2551 -0.9843,-1.8081 -1.9227,-3.544 -2.841,-5.2164 -0.8723,-1.6097 -1.717,-3.1637 -2.5339,-4.6618 C 10.6026,31.0236 9.6766,32.747 8.73534,34.4954 6.3248,33.6 4.47098,32.1703 3.722,29.8971 3.01031,27.7396 3.28293,24.8108 5.00188,20.8624 Z"
|
||||
fill="#fec827"/>
|
||||
<path
|
||||
d="M 29.2163,1.95544 C 22.9346,3.77221 18.7333,6.22729 15.9268,8.91633 13.3887,11.2767 11.6132,14.3497 10.8302,17.7373 l -4.26434,1.4182 c 0.92021,0.9878 1.87008,2.0054 2.8496,3.0529 1.01774,1.0861 2.07004,2.2096 3.15664,3.3707 1.4808,-1.9583 2.9414,-3.8838 4.382,-5.7766 1.4148,-1.8486 2.8066,-3.6827 4.1697,-5.4879 -1.6912,0.5604 -3.3662,1.112 -5.0248,1.655 0.673,-2.7427 1.9941,-5.2813 3.8511,-7.39987 2.0088,-2.31067 4.9502,-4.55779 9.2662,-6.6114 z"
|
||||
fill="#f58120"/>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(25)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
d="M 86.078,37 78.3011,24.0818 c -0.2784,-0.464 -0.6125,-0.761 -1.1322,-0.761 -0.5197,0 -0.8723,0.297 -1.1507,0.761 L 68.2598,37 h 2.6913 l 6.125,-10.3568 3.0069,5.0485 h -4.1391 l -1.1136,1.9117 h 6.3849 L 83.2383,37 Z m 13.1331,0 v -2.3572 h -7.3129 c -2.4314,0 -4.1947,-1.8746 -4.1947,-4.4174 0,-2.5614 1.7819,-4.3618 4.1947,-4.3618 h 7.3129 v -2.3572 h -7.3129 c -3.7678,0 -6.5519,2.6356 -6.5519,6.6076 0,3.9163 2.7841,6.886 6.5519,6.886 z m 13.9779,0 v -2.3572 h -9.726 v -8.7792 h 9.708 V 23.5064 H 101.125 V 37 Z m -0.52,-5.8652 v -1.9859 h -7.479 v 1.9859 z M 130.78,37 c 2.821,0 4.194,-1.689 4.194,-3.9534 0,-2.6171 -1.466,-3.8977 -4.194,-3.8977 h -6.181 c -1.039,0 -1.689,-0.6125 -1.689,-1.6334 0,-1.0208 0.631,-1.6519 1.708,-1.6519 h 9.688 v -2.3572 h -9.688 c -2.692,0 -4.158,1.6148 -4.158,3.7864 0,2.2273 1.578,3.842 4.12,3.842 h 6.2 c 1.262,0 1.967,0.594 1.967,1.7447 0,1.058 -0.705,1.7633 -1.967,1.7633 H 120.664 V 37 Z m 12.897,0 V 25.8636 h 5.123 v -2.3572 h -12.603 v 2.3572 h 5.123 V 37 Z m 22.546,0 -4.065,-4.1019 c 1.745,-0.6682 2.784,-2.2458 2.784,-4.4731 0,-3.0068 -1.818,-4.9186 -4.9,-4.9186 h -9.688 V 37 h 2.338 V 25.8636 h 7.35 c 1.615,0 2.58,0.8538 2.58,2.5985 0,1.7447 -0.891,2.7099 -2.58,2.7099 h -5.623 v 2.0788 h 5.048 L 162.882,37 Z m 13.288,0 v -2.3572 h -9.726 v -8.7792 h 9.707 V 23.5064 H 167.446 V 37 Z m -0.52,-5.8652 v -1.9859 h -7.48 v 1.9859 z M 198.439,37 190.662,24.0818 c -0.279,-0.464 -0.613,-0.761 -1.133,-0.761 -0.519,0 -0.872,0.297 -1.15,0.761 L 180.62,37 h 2.692 l 6.125,-10.3568 3.006,5.0485 h -4.139 l -1.113,1.9117 h 6.385 L 195.599,37 Z m 20.588,0 -3.452,-12.6955 c -0.186,-0.6496 -0.557,-0.9651 -1.132,-0.9651 -0.502,0 -0.91,0.2784 -1.133,0.7981 l -4.157,9.5402 -4.158,-9.5402 c -0.222,-0.5197 -0.687,-0.7981 -1.188,-0.7981 -0.631,0 -1.002,0.3155 -1.169,0.9651 L 199.167,37 h 2.32 l 2.58,-9.4845 3.805,8.7792 c 0.241,0.5754 0.65,0.8909 1.207,0.8909 0.575,0 0.946,-0.3155 1.206,-0.8909 l 3.823,-8.7792 2.58,9.4845 z"
|
||||
fill="#F2F2F2"
|
||||
style="fill:#000000"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs
|
||||
id="defs23">
|
||||
<clipPath
|
||||
id="clip0_205_625">
|
||||
<rect
|
||||
width="66"
|
||||
height="59"
|
||||
fill="#ffffff"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.5 KiB |
BIN
images/Broadcast.png
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
images/Client-server-model.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
images/Docker-network-1.png
Normal file
After Width: | Height: | Size: 167 KiB |
BIN
images/JSON-Wire-Protocol-over-HTTP-Client.png
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
images/Multicast.png
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
images/NAT_Concept-en.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
images/Unicast.png
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
images/chrome_KEAVUAGKCE.png
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
images/chrome_Mqt9TamrBT.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 39 KiB |
BIN
images/crud-annotated-document.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
images/docker-engine.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
images/grafana-default-latency.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
images/grafana-high-latency.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
images/hetzner-cloud-datacenters.png
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
images/jsep-architecture-diagram_856.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
images/mermaid-diagram-2025-03-22-144148.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
images/mongodb-pipelines.png
Normal file
After Width: | Height: | Size: 153 KiB |
BIN
images/nat-traversal.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
images/p2p-assisted-media-delivery.CQdChaOy.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
images/p2p-media-loader-network.CFcphAUE.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
images/p2p-network.png
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
images/python-logo-master-v3-TM.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
images/selenium-grid.jpg
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
images/stun-server.png
Normal file
After Width: | Height: | Size: 73 KiB |
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="436.33716mm"
|
||||
height="115.63236mm"
|
||||
viewBox="0 0 436.33716 115.63236"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(124.123,6.0733085)">
|
||||
<g
|
||||
id="g1151"
|
||||
transform="matrix(6.0868818,0,0,6.0868818,-732.64487,-599.37985)">
|
||||
<path
|
||||
d="m 106.18342,105.84175 -6.206843,-0.014 -0.0039,1.38039 2.311913,-0.0118 0.0551,8.95356 1.50888,0.004 0.0567,-8.94074 2.27595,-0.0157 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path118" />
|
||||
<path
|
||||
d="m 110.34872,115.0306 c -1.13152,0 -1.74862,-0.91223 -1.74862,-2.04375 0,-1.1464 0.59805,-2.14886 1.72957,-2.14886 1.14641,0 1.94857,0.98264 1.94857,2.12905 0,1.13152 -0.78311,2.06356 -1.92952,2.06356 z m -3.39272,-2.0085 c 0,1.81638 1.45951,3.39031 3.14189,3.39031 1.25063,0 1.56823,-0.50841 1.98511,-0.94018 l 0.002,0.69436 1.61201,0.0147 10e-4,-6.52031 -1.54718,-10e-5 -0.009,0.82355 c -0.3871,-0.43176 -0.85501,-0.93861 -2.18008,-0.93861 -1.72705,0 -3.00491,1.64503 -3.00491,3.4763 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path120" />
|
||||
<path
|
||||
d="m 115.52271,116.2074 1.45658,0.004 -0.0112,-3.63171 c -0.003,-0.81885 0.66275,-1.73405 1.57094,-1.73405 0.87841,0 1.16366,0.8754 1.1648,1.70967 l 0.0149,3.65424 1.36565,0.004 2.4e-4,-3.89183 c 9e-5,-1.38462 -0.48318,-2.75908 -2.2549,-2.75908 -0.95285,0 -1.5389,0.24611 -1.74733,0.70765 l -0.0905,-5.39541 -1.46273,-6e-5 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path122" />
|
||||
<g
|
||||
aria-label="e"
|
||||
id="text104"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
transform="translate(0,20.10834)">
|
||||
<path
|
||||
d="m 135.77525,91.943817 -2.96622,-0.0168 c 0.16377,-0.595536 0.64709,-1.155374 1.48084,-1.155374 0.87841,0 1.24717,0.546865 1.48538,1.172178 z m -4.69876,0.837883 c 0,1.816384 1.32897,3.493078 3.17513,3.493078 1.4144,0 2.94259,-1.236045 3.02569,-1.876867 l -1.1049,-0.644681 c -0.23822,0.297768 -1.15358,1.149409 -1.83845,1.149409 -1.20596,0 -1.65729,-0.781712 -1.65729,-1.779235 l 4.70278,0.02339 c 0,-1.995046 -1.22489,-3.863459 -3.04127,-3.863459 -1.83127,0 -3.26169,1.815975 -3.26169,3.498365 z"
|
||||
style="fill:#000000;stroke-width:0.372211"
|
||||
id="path125" />
|
||||
</g>
|
||||
<path
|
||||
d="m 143.99337,114.6648 -0.14168,-8.87341 -1.52294,0.0141 -0.006,10.37368 4.54697,0.0624 -0.004,-1.57754 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path131" />
|
||||
<path
|
||||
d="m 152.26578,105.18986 -5.02661,10.95056 1.77251,0.004 0.89789,-2.25292 h 4.53055 l 1.0435,2.24762 1.79729,-6.4e-4 z m 0.0602,3.49642 1.59306,3.51807 -3.18612,0.0503 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path133" />
|
||||
<path
|
||||
d="m 163.81296,106.92399 v -1.51863 h -5.10672 v 10.63037 h 1.51862 v -4.63031 h 3.5881 v -1.51863 h -3.5881 l -0.0169,-2.95443 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path135" />
|
||||
<path
|
||||
d="m 170.19028,108.0662 1.2443,-0.72871 c -0.25077,-0.75392 -1.62879,-1.74534 -2.77519,-1.74534 -1.69728,0 -3.15731,0.98874 -3.15731,2.55203 0,1.72706 0.71475,2.32326 2.11426,2.88902 1.38462,0.55088 2.29961,1.07985 2.43537,1.88315 0.24596,1.45537 -0.99727,2.16559 -1.92136,2.00482 -1.06332,-0.18499 -1.49181,-1.21306 -1.53913,-1.87861 l -1.49173,0.31661 c 0,0.89704 1.17066,2.91752 2.85466,2.98186 2.13863,0.0817 3.70337,-1.31771 3.70337,-3.2681 0,-1.99506 -2.16524,-2.91815 -3.47542,-3.42435 -1.10174,-0.43177 -1.02351,-0.78545 -1.02351,-1.52987 0,-0.65509 0.45097,-1.02986 1.37405,-1.02986 0.69975,0 1.27055,0.60514 1.65764,0.97735 z"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
id="path137" />
|
||||
<g
|
||||
aria-label="-"
|
||||
id="text116"
|
||||
style="font-size:14.8884px;line-height:1.25;font-family:SansSerifFLF;-inkscape-font-specification:SansSerifFLF;fill:#000000;stroke-width:0.372211"
|
||||
transform="translate(0,20.10834)">
|
||||
<path
|
||||
d="m 141.06065,92.121323 h -2.90455 v 1.312631 h 2.90455 z"
|
||||
style="fill:#000000;stroke-width:0.384512"
|
||||
id="path128" />
|
||||
</g>
|
||||
<circle
|
||||
style="fill:#ff0000;fill-opacity:1;stroke-width:0.236763"
|
||||
id="path162"
|
||||
cx="126.39951"
|
||||
cy="112.9655"
|
||||
r="3.5044639" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 124.91938,109.77401 -0.80485,-2.07788 -0.56046,0.0391 -0.12405,-2.01283 1.46712,1.61746 -0.37275,0.20586 0.89001,2.09631 z"
|
||||
id="path164" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 127.35787,109.53011 0.78896,-1.96229 -0.67822,-0.17271 1.42086,-1.68544 0.036,2.13358 -0.27114,-0.12937 -0.88072,2.11195 z"
|
||||
id="path989" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 125.90599,109.56222 0.0869,-5.48256 -0.55471,-0.15529 0.73255,-1.98112 0.84234,2.08361 -0.48508,0.0646 0.03,5.50009 z"
|
||||
id="path991" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.233764"
|
||||
id="path993"
|
||||
cx="122.51501"
|
||||
cy="103.5195"
|
||||
r="2.0734317" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.233764"
|
||||
id="circle995"
|
||||
cx="126.19068"
|
||||
cy="99.546417"
|
||||
r="2.0734317" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.233764"
|
||||
id="circle997"
|
||||
cx="129.90472"
|
||||
cy="103.51728"
|
||||
r="2.0734317" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.2 KiB |
BIN
images/telegraf-architecture-diagram.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
images/web-rtc-internals-chrome.png
Normal file
After Width: | Height: | Size: 537 KiB |
BIN
images/which-browser-supports-selenium-1.png
Normal file
After Width: | Height: | Size: 937 KiB |
1
peertube/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# TODO
|
103
peertube/datavis/CRUD/player-statistics.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/* global use, db */
|
||||
// MongoDB Playground
|
||||
// To disable this template go to Settings | MongoDB | Use Default Template For Playground.
|
||||
// Make sure you are connected to enable completions and to be able to run a playground.
|
||||
// Use Ctrl+Space inside a snippet or a string literal to trigger completions.
|
||||
// The result of the last command run in a playground is shown on the results panel.
|
||||
// By default the first 20 documents will be returned with a cursor.
|
||||
// Use 'console.log()' to print to the debug output.
|
||||
// For more documentation on playgrounds please refer to
|
||||
// https://www.mongodb.com/docs/mongodb-vscode/playgrounds/
|
||||
|
||||
// Select the database to use.
|
||||
use('statistics');
|
||||
|
||||
// Insert a few documents into the sales collection.
|
||||
db.getCollection('peertube_hetzner_high_latency').aggregate(
|
||||
[
|
||||
{
|
||||
$group: {
|
||||
_id: "$tags.session",
|
||||
maxDownPeers: {
|
||||
$max: "$player.Download Breakdown.Peers"
|
||||
},
|
||||
maxDownServer: {
|
||||
$max: "$player.Download Breakdown.Server"
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalDownPeers: {
|
||||
$sum: "$maxDownPeers"
|
||||
},
|
||||
totalDownServer: {
|
||||
$sum: "$maxDownServer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
totalDownPeers: 1,
|
||||
totalDownServer: 1,
|
||||
totalComputedDown: {
|
||||
$sum: [
|
||||
"$totalDownPeers",
|
||||
"$totalDownServer"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
totalDownPeers: 1,
|
||||
totalDownServer: 1,
|
||||
totalComputedDown: 1,
|
||||
percentageOfTotalPeers: {
|
||||
$round: {
|
||||
$multiply: [
|
||||
{
|
||||
$divide: [
|
||||
"$totalDownPeers",
|
||||
"$totalComputedDown"
|
||||
]
|
||||
},
|
||||
100
|
||||
]
|
||||
}
|
||||
},
|
||||
percentageOfTotalServer: {
|
||||
$round: {
|
||||
$multiply: [
|
||||
{
|
||||
$divide: [
|
||||
"$totalDownServer",
|
||||
"$totalComputedDown"
|
||||
]
|
||||
},
|
||||
100
|
||||
]
|
||||
}
|
||||
},
|
||||
ratio: {
|
||||
$round: {
|
||||
$divide: [
|
||||
"$totalDownPeers",
|
||||
"$totalDownServer"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: {
|
||||
ratio: -1
|
||||
}
|
||||
},
|
||||
{
|
||||
$limit: 10
|
||||
}
|
||||
]
|
||||
)
|
255
peertube/datavis/CRUD/webrtc-statistics.js
Normal file
@@ -0,0 +1,255 @@
|
||||
/* global use, db */
|
||||
// MongoDB Playground
|
||||
// To disable this template go to Settings | MongoDB | Use Default Template For Playground.
|
||||
// Make sure you are connected to enable completions and to be able to run a playground.
|
||||
// Use Ctrl+Space inside a snippet or a string literal to trigger completions.
|
||||
// The result of the last command run in a playground is shown on the results panel.
|
||||
// By default the first 20 documents will be returned with a cursor.
|
||||
// Use 'console.log()' to print to the debug output.
|
||||
// For more documentation on playgrounds please refer to
|
||||
// https://www.mongodb.com/docs/mongodb-vscode/playgrounds/
|
||||
use("statistics");
|
||||
|
||||
let formattedDate = (date) => ({
|
||||
unix: { $toLong: date },
|
||||
iso: { $toString: date },
|
||||
});
|
||||
|
||||
// Find the minimum timestamp and calculate the maximum timestamp (one hour later) for a collection
|
||||
function getTimeWindow(collectionName) {
|
||||
const minTimestamp = db.getCollection(collectionName).aggregate([
|
||||
{ $sort: { timestamp: 1 } },
|
||||
{ $limit: 1 },
|
||||
{ $project: { _id: 0, timestamp: 1 } }
|
||||
]).toArray()[0]?.timestamp;
|
||||
|
||||
if (!minTimestamp) return null;
|
||||
|
||||
const maxTimestamp = new Date(minTimestamp.getTime() + 60 * 60 * 1000);
|
||||
return { minTimestamp, maxTimestamp };
|
||||
}
|
||||
|
||||
// Get time windows for both collections
|
||||
const highLatencyTimeWindow = getTimeWindow("peertube_hetzner_high_latency");
|
||||
const defaultLatencyTimeWindow = getTimeWindow("peertube_hetzner_default_latency");
|
||||
|
||||
// Function to perform the aggregation for a given collection
|
||||
function getAggregationResult(collectionName, timeWindow) {
|
||||
if (!timeWindow) return [];
|
||||
|
||||
return db.getCollection(collectionName).aggregate([
|
||||
// Filter documents within the collection's specific time window
|
||||
{
|
||||
$match: {
|
||||
timestamp: {
|
||||
$gte: timeWindow.minTimestamp,
|
||||
$lt: timeWindow.maxTimestamp
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$unwind: "$peers"
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
"peers.iceConnectionState": "connected"
|
||||
}
|
||||
},
|
||||
{
|
||||
$unwind: "$peers.values"
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
"peers.values.type": "data-channel",
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: "$peers.id",
|
||||
maxBytesReceived: {
|
||||
$max: "$peers.values.bytesReceived"
|
||||
},
|
||||
maxBytesSent: {
|
||||
$max: "$peers.values.bytesSent"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalMaxBytesReceived: {
|
||||
$sum: "$maxBytesReceived"
|
||||
},
|
||||
totalMaxBytesSent: {
|
||||
$sum: "$maxBytesSent"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
collection: collectionName,
|
||||
totalBytesReceived: "$totalMaxBytesReceived",
|
||||
totalBytesSent: "$totalMaxBytesSent",
|
||||
formattedBytesReceived: {
|
||||
$switch: {
|
||||
branches: [
|
||||
{
|
||||
case: {
|
||||
$gte: [
|
||||
"$totalMaxBytesReceived",
|
||||
1073741824 // 1024^3
|
||||
]
|
||||
},
|
||||
then: {
|
||||
$concat: [
|
||||
{
|
||||
$toString: {
|
||||
$divide: [
|
||||
"$totalMaxBytesReceived",
|
||||
1073741824 // 1024^3
|
||||
]
|
||||
}
|
||||
},
|
||||
" GiB"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
case: {
|
||||
$gte: [
|
||||
"$totalMaxBytesReceived",
|
||||
1048576 // 1024^2
|
||||
]
|
||||
},
|
||||
then: {
|
||||
$concat: [
|
||||
{
|
||||
$toString: {
|
||||
$divide: [
|
||||
"$totalMaxBytesReceived",
|
||||
1048576 // 1024^2
|
||||
]
|
||||
}
|
||||
},
|
||||
" MiB"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
case: {
|
||||
$gte: [
|
||||
"$totalMaxBytesReceived",
|
||||
1024 // 1024^1
|
||||
]
|
||||
},
|
||||
then: {
|
||||
$concat: [
|
||||
{
|
||||
$toString: {
|
||||
$divide: [
|
||||
"$totalMaxBytesReceived",
|
||||
1024 // 1024^1
|
||||
]
|
||||
}
|
||||
},
|
||||
" KiB"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
default: {
|
||||
$concat: [
|
||||
{
|
||||
$toString:
|
||||
"$totalMaxBytesReceived"
|
||||
},
|
||||
" bytes"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
formattedBytesSent: {
|
||||
$switch: {
|
||||
branches: [
|
||||
{
|
||||
case: {
|
||||
$gte: [
|
||||
"$totalMaxBytesSent",
|
||||
1073741824 // 1024^3
|
||||
]
|
||||
},
|
||||
then: {
|
||||
$concat: [
|
||||
{
|
||||
$toString: {
|
||||
$divide: [
|
||||
"$totalMaxBytesSent",
|
||||
1073741824 // 1024^3
|
||||
]
|
||||
}
|
||||
},
|
||||
" GiB"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
case: {
|
||||
$gte: [
|
||||
"$totalMaxBytesSent",
|
||||
1048576 // 1024^2
|
||||
]
|
||||
},
|
||||
then: {
|
||||
$concat: [
|
||||
{
|
||||
$toString: {
|
||||
$divide: [
|
||||
"$totalMaxBytesSent",
|
||||
1048576 // 1024^2
|
||||
]
|
||||
}
|
||||
},
|
||||
" MiB"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
case: {
|
||||
$gte: ["$totalMaxBytesSent", 1024] // 1024^1
|
||||
},
|
||||
then: {
|
||||
$concat: [
|
||||
{
|
||||
$toString: {
|
||||
$divide: [
|
||||
"$totalMaxBytesSent",
|
||||
1024 // 1024^1
|
||||
]
|
||||
}
|
||||
},
|
||||
" KiB"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
default: {
|
||||
$concat: [
|
||||
{ $toString: "$totalMaxBytesSent" },
|
||||
" bytes"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]).toArray();
|
||||
}
|
||||
|
||||
// Get results from both collections using their respective time windows
|
||||
const highLatencyResults = getAggregationResult("peertube_hetzner_high_latency", highLatencyTimeWindow);
|
||||
const defaultLatencyResults = getAggregationResult("peertube_hetzner_default_latency", defaultLatencyTimeWindow);
|
||||
|
||||
// Combine and return the results
|
||||
const combinedResults = [...highLatencyResults, ...defaultLatencyResults];
|
||||
combinedResults;
|
3835
peertube/datavis/plot.ipynb
Normal file
@@ -1,5 +1,3 @@
|
||||
selenium
|
||||
beautifulsoup4
|
||||
pymongo
|
||||
pandas
|
||||
matplotlib
|
1
peertube/statnerd
Submodule
@@ -1,17 +0,0 @@
|
||||
## Statistiche per nerd da Peer Tube
|
||||
|
||||
spero funzioni
|
||||
|
||||
per installarlo baste creare un nuovo ambiente e installare le dipendenze con:
|
||||
|
||||
```
|
||||
python -m venv env
|
||||
source .\env\bin\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
per eseguire il tutto basta lanciare:
|
||||
|
||||
```
|
||||
jupyter-lab
|
||||
```
|
@@ -1,190 +0,0 @@
|
||||
import signal
|
||||
import json
|
||||
import time
|
||||
import socket
|
||||
import logging
|
||||
import os
|
||||
from functools import partial
|
||||
from http.server import HTTPServer
|
||||
from utils.PostHandler import Handler
|
||||
from utils.ColoredFormatter import ColoredFormatter
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver import ActionChains
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def setupLogger():
|
||||
logging_format = "[%(asctime)s] (%(levelname)s) %(module)s - %(funcName)s: %(message)s"
|
||||
logging.basicConfig(level=logging.INFO, format=logging_format)
|
||||
(logger := logging.getLogger(__name__)).setLevel(logging.INFO)
|
||||
logger.propagate = False
|
||||
(logger_handler := logging.StreamHandler()).setFormatter(
|
||||
ColoredFormatter(fmt=logging_format)
|
||||
)
|
||||
logger.addHandler(logger_handler)
|
||||
|
||||
def interrupt_handler(signum, driver: webdriver.Chrome):
|
||||
logger.info(f'Handling signal {signum} ({signal.Signals(signum).name}).')
|
||||
|
||||
driver.quit()
|
||||
raise SystemExit
|
||||
|
||||
def setupChromeDriver():
|
||||
logger.log(logging.INFO, 'Setting up Chrome driver.')
|
||||
chrome_options = Options()
|
||||
#chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--mute-audio")
|
||||
chrome_options.add_argument("--window-size=1280,720")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
chrome_options.add_argument("--no-default-browser-check")
|
||||
chrome_options.add_argument("--disable-features=WebRtcHideLocalIpsWithMdns")
|
||||
chrome_options.add_argument(f"--load-extension={os.path.abspath(os.path.join(os.path.dirname(__file__), 'webrtc-internals-exporter'))}")
|
||||
chrome_options.add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'})
|
||||
|
||||
driver = webdriver.Chrome(options=chrome_options)
|
||||
#driver = webdriver.Remote(command_executor='http://localhost:4444', options=chrome_options)
|
||||
logger.log(logging.INFO, 'Chrome driver setup complete.')
|
||||
|
||||
return driver
|
||||
|
||||
def saveStats(stats: list):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
logger.log(logging.DEBUG, f'Saving stats: {json.dumps(stats, indent=4)}')
|
||||
sock.sendto(json.dumps(stats).encode(), ('localhost', 8094))
|
||||
sock.close()
|
||||
logger.log(logging.DEBUG, 'Sent stats to socket.')
|
||||
except socket.error as e:
|
||||
logger.error(f'Got socket error: {e}')
|
||||
|
||||
def downloadStats(driver: webdriver.Chrome, peersDict: dict):
|
||||
html = driver.find_element(By.CLASS_NAME ,'vjs-stats-list').get_attribute('innerHTML')
|
||||
if html is not None:
|
||||
htmlBS = bs(html, 'html.parser')
|
||||
else:
|
||||
raise ValueError("html is None")
|
||||
|
||||
stats = htmlBS.find_all('div', attrs={'style': 'display: block;'})
|
||||
|
||||
playerStats = {
|
||||
stat.div.text: stat.span.text.replace('\u21d3', 'down').replace('down/', 'down /').replace('\u21d1 ', 'up').replace('\u21d1', 'up').replace('\u00b7', '-').strip()
|
||||
for stat in stats
|
||||
}
|
||||
|
||||
keys = list(playerStats.keys())
|
||||
for stat in keys:
|
||||
if 'Viewport / Frames' == stat:
|
||||
viewport, frames = playerStats[stat].split(' / ')
|
||||
width, height = viewport.split('x')
|
||||
height, devicePixelRatio = height.split('*')
|
||||
dropped, total = frames.split(' of ')[0].split()[0], frames.split(' of ')[1].split()[0]
|
||||
playerStats[stat] = {'Width': int(width), 'Height': int(height), 'Pixel ratio': float(devicePixelRatio), 'Frames': {'Dropped': int(dropped), 'Total': int(total)}}
|
||||
|
||||
if 'Codecs' == stat:
|
||||
video, audio = playerStats[stat].split(' / ')
|
||||
playerStats[stat] = {'Video': video, 'Audio': audio}
|
||||
|
||||
if 'Volume' == stat:
|
||||
if ' (' in playerStats[stat]:
|
||||
volume, muted = playerStats[stat].split(' (')
|
||||
playerStats[stat] = {'Volume': int(volume), 'Muted': 'muted' in muted}
|
||||
else:
|
||||
playerStats[stat] = {'Volume': int(playerStats[stat]), 'Muted': False}
|
||||
|
||||
if 'Connection Speed' == stat:
|
||||
speed, unit = playerStats[stat].split()
|
||||
|
||||
speedBytes = int(speed) * (1024 ** {'B/s': 0, 'KB/s': 1, 'MB/s': 2, 'GB/s': 3}[unit])
|
||||
|
||||
playerStats[stat] = {'Speed': int(speedBytes), 'Granularity': 's'}
|
||||
|
||||
if 'Network Activity' == stat:
|
||||
downString, upString = playerStats[stat].split(' / ')
|
||||
|
||||
down, downUnit = downString.replace('down', '').strip().split()
|
||||
up, upUnit = upString.replace('up', '').strip().split()
|
||||
|
||||
downBytes = int(down) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[downUnit])
|
||||
upBytes = int(up) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[upUnit])
|
||||
|
||||
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
|
||||
|
||||
if 'Total Transfered' == stat:
|
||||
downString, upString = playerStats[stat].split(' / ')
|
||||
|
||||
down, downUnit = downString.replace('down', '').strip().split()
|
||||
up, upUnit = upString.replace('up', '').strip().split()
|
||||
|
||||
downBytes = int(down) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[downUnit])
|
||||
upBytes = int(up) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[upUnit])
|
||||
|
||||
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
|
||||
|
||||
if 'Download Breakdown' == stat:
|
||||
server, peer = playerStats[stat].split(' - ')
|
||||
|
||||
server, serverUnit = server.replace('from servers', '').strip().split()
|
||||
peer, peerUnit = peer.replace('from peers', '').strip().split()
|
||||
|
||||
serverBytes = int(server) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[serverUnit])
|
||||
peerBytes = int(peer) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[peerUnit])
|
||||
|
||||
playerStats[stat] = {'Server': serverBytes, 'Peers': peerBytes}
|
||||
|
||||
if 'Buffer State' == stat:
|
||||
del(playerStats[stat])
|
||||
|
||||
if 'Live Latency' == stat:
|
||||
latency, edge = playerStats[stat].split(' (from edge: ')
|
||||
latency = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in latency.replace('s', '').split('m') if part])))
|
||||
edge = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in edge.replace('s', '').replace(')', '').split('m') if part])))
|
||||
playerStats[stat] = {'Latency': latency, 'Edge': edge}
|
||||
|
||||
stats = {
|
||||
'player': playerStats,
|
||||
'peers': peersDict,
|
||||
'url': driver.current_url,
|
||||
'timestamp': int(time.time() * 1000),
|
||||
'session': driver.session_id
|
||||
}
|
||||
|
||||
saveStats([stats])
|
||||
|
||||
def setupStats(driver: webdriver.Chrome, url: str):
|
||||
logger.log(logging.INFO, 'Setting up stats.')
|
||||
actions = ActionChains(driver)
|
||||
wait = WebDriverWait(driver, 30, poll_frequency=0.2)
|
||||
|
||||
driver.get(url)
|
||||
|
||||
wait.until(ec.presence_of_element_located((By.CLASS_NAME, 'vjs-big-play-button')))
|
||||
actions.click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
|
||||
wait.until(ec.visibility_of_element_located((By.CLASS_NAME, 'vjs-control-bar')))
|
||||
actions.context_click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
|
||||
statsForNerds = driver.find_elements(By.CLASS_NAME ,'vjs-menu-item')
|
||||
actions.click(statsForNerds[-1]).perform()
|
||||
wait.until(ec.text_to_be_present_in_element((By.CLASS_NAME, 'vjs-stats-list'), 'Player'))
|
||||
actions.move_to_element(driver.find_element(By.CLASS_NAME ,'vjs-control-bar')).perform()
|
||||
logger.log(logging.INFO, 'Stats setup complete.')
|
||||
|
||||
return driver
|
||||
|
||||
if __name__ == '__main__':
|
||||
setupLogger()
|
||||
|
||||
driver = setupChromeDriver()
|
||||
|
||||
signal.signal(signal.SIGINT, lambda signum, frame: interrupt_handler(signum, driver))
|
||||
|
||||
setupStats(driver, "https://tube.kobim.cloud/w/iN2T8PmbSb4HJTDA2rV3sg")
|
||||
|
||||
logger.log(logging.INFO, 'Starting server collector.')
|
||||
httpd = HTTPServer(('localhost', 9092), partial(Handler, downloadStats, driver, logger))
|
||||
logger.info('Server collector started.')
|
||||
httpd.serve_forever()
|
@@ -1,287 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import seaborn as sns\n",
|
||||
"import plotly.express as px\n",
|
||||
"import plotly.graph_objects as pgo\n",
|
||||
"import scipy as sp\n",
|
||||
"from pymongo import MongoClient\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"client = MongoClient(\"mongodb://stats_user:%40z%5EVFhN7q%25vzit@192.168.86.120:27017/?authSource=statistics\")\n",
|
||||
"db = client.statistics\n",
|
||||
"\n",
|
||||
"formatted_date = lambda date: {\n",
|
||||
" \"unix\": {\"$toLong\": date},\n",
|
||||
" \"iso\": {\"$toString\": date},\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"pipeline = [\n",
|
||||
" {\"$sort\": {\"timestamp\": 1}},\n",
|
||||
" {\n",
|
||||
" \"$group\": {\n",
|
||||
" \"_id\": \"$tags.session\",\n",
|
||||
" \"host\": {\"$first\": \"$tags.host\"},\n",
|
||||
" \"firstTimestamp\": {\"$first\": \"$timestamp\"},\n",
|
||||
" \"lastTimestamp\": {\"$last\": \"$timestamp\"},\n",
|
||||
" \"firstTimestampWithPeers\": {\n",
|
||||
" \"$min\": {\n",
|
||||
" \"$cond\": {\n",
|
||||
" \"if\": {\"$gt\": [{\"$size\": {\"$ifNull\": [\"$peers\", []]}}, 0]},\n",
|
||||
" \"then\": \"$timestamp\",\n",
|
||||
" \"else\": None,\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"maxNumberOfPeers\": {\n",
|
||||
" \"$max\": {\"$size\": {\"$ifNull\": [\"$peers\", []]}},\n",
|
||||
" },\n",
|
||||
" \"minNumberOfPeers\": {\n",
|
||||
" \"$min\": {\"$size\": {\"$ifNull\": [\"$peers\", []]}},\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"$lookup\": {\n",
|
||||
" \"from\": \"peertube_ts\",\n",
|
||||
" \"let\": {\"currentSession\": \"$_id\", \"ftp\": \"$firstTimestampWithPeers\", \"fst\": \"$firstTimestamp\"},\n",
|
||||
" \"pipeline\": [\n",
|
||||
" {\n",
|
||||
" \"$match\": {\n",
|
||||
" \"$expr\": {\n",
|
||||
" \"$and\": [\n",
|
||||
" {\"$ne\": [\"$tags.session\", \"$$currentSession\"]},\n",
|
||||
" {\"$lt\": [\"$timestamp\", \"$$ftp\"]},\n",
|
||||
" {\"$gte\": [\"$timestamp\", \"$$fst\"]},\n",
|
||||
" {\"$gt\": [{\"$size\": {\"$ifNull\": [\"$peers\", []]}}, 0]},\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" ],\n",
|
||||
" \"as\": \"concurrentSessions\",\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"$addFields\": {\n",
|
||||
" \"concurrentSessions\": {\"$gt\": [{\"$size\": \"$concurrentSessions\"}, 0]}\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"$group\": {\n",
|
||||
" \"_id\": \"$host\",\n",
|
||||
" \"sessions\": {\n",
|
||||
" \"$push\": {\n",
|
||||
" \"id\": \"$_id\",\n",
|
||||
" \"startTime\": formatted_date(\"$firstTimestamp\"),\n",
|
||||
" \"endTime\": formatted_date(\"$lastTimestamp\"),\n",
|
||||
" \"duration\": {\n",
|
||||
" \"$divide\": [\n",
|
||||
" {\"$subtract\": [\"$lastTimestamp\", \"$firstTimestamp\"]},\n",
|
||||
" 1000,\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" \"firstPeerConnection\": {\n",
|
||||
" \"$cond\": {\n",
|
||||
" \"if\": {\"$eq\": [\"$firstTimestampWithPeers\", None]},\n",
|
||||
" \"then\": None,\n",
|
||||
" \"else\": {\n",
|
||||
" \"time\": {\n",
|
||||
" \"date\": formatted_date(\"$firstTimestampWithPeers\"),\n",
|
||||
" \"elapsedFromStart\": {\n",
|
||||
" \"$divide\": [\n",
|
||||
" {\"$subtract\": [\"$firstTimestampWithPeers\", \"$firstTimestamp\"]},\n",
|
||||
" 1000,\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"concurrentSessions\": \"$concurrentSessions\",\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"maxPeers\": {\"$max\": \"$maxNumberOfPeers\"},\n",
|
||||
" \"minPeers\": {\"$min\": \"$minNumberOfPeers\"},\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"$set\": {\n",
|
||||
" \"sessions\": {\n",
|
||||
" \"$sortArray\": {\n",
|
||||
" \"input\": \"$sessions\",\n",
|
||||
" \"sortBy\": {\"id\": 1},\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"$project\": {\n",
|
||||
" \"_id\": 0,\n",
|
||||
" \"host\": \"$_id\",\n",
|
||||
" \"sessions\": \"$sessions\",\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" {\"$sort\": {\"host\": 1}},\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"result = db.peertube_ts.aggregate(pipeline)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Extract data from the result cursor\n",
|
||||
"data = []\n",
|
||||
"for host in result:\n",
|
||||
" for session in host['sessions']:\n",
|
||||
" if session['firstPeerConnection'] and session['firstPeerConnection']['time']:\n",
|
||||
" elapsed = session['firstPeerConnection']['time']['elapsedFromStart']\n",
|
||||
" concurrent_sessions = session['firstPeerConnection']['concurrentSessions']\n",
|
||||
" data.append((elapsed, concurrent_sessions))\n",
|
||||
"\n",
|
||||
"# Convert to a DataFrame for easier plotting\n",
|
||||
"df = pd.DataFrame(data, columns=['Elapsed', 'ConcurrentSessions'])\n",
|
||||
"\n",
|
||||
"# Convert boolean column to integers\n",
|
||||
"df['ConcurrentSessions'] = df['ConcurrentSessions'].astype(int)\n",
|
||||
"\n",
|
||||
"# Print some statistics\n",
|
||||
"print(\"Mean time until first peer connection: {:.2f}s\".format(df['Elapsed'].mean()))\n",
|
||||
"print(\"Median time until first peer connection: {:.2f}s\".format(df['Elapsed'].median()))\n",
|
||||
"print(\"Number of sessions with concurrent sessions: {}\".format(df['ConcurrentSessions'].sum()))\n",
|
||||
"print(\"Number of sessions without concurrent sessions: {}\".format(df['ConcurrentSessions'].count() - df['ConcurrentSessions'].sum()))\n",
|
||||
"\n",
|
||||
"# Revert concurrent sessions column to boolean for plotting\n",
|
||||
"df['ConcurrentSessions'] = df['ConcurrentSessions'].astype(bool)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot the histogram of the elapsed time until first peer connection\n",
|
||||
"# Color the bars based on the number of concurrent sessions and add a legend\n",
|
||||
"fig = px.histogram(df, x='Elapsed', color='ConcurrentSessions', barmode='overlay', nbins=100)\n",
|
||||
"fig.update_layout(\n",
|
||||
" title='Elapsed time until first peer connection',\n",
|
||||
" xaxis_title='Elapsed time (s)',\n",
|
||||
" yaxis_title='Count',\n",
|
||||
" legend_title='Had concurrent sessions',\n",
|
||||
")\n",
|
||||
"fig.show()\n",
|
||||
"\n",
|
||||
"# Plot the line chart of the elapsed time until first peer connection\n",
|
||||
"fig = px.line(df, x=df.index, y='Elapsed', markers=True)\n",
|
||||
"fig.update_layout(\n",
|
||||
" title='Elapsed time until first peer connection',\n",
|
||||
" xaxis_title='Session index',\n",
|
||||
" yaxis_title='Elapsed time (s)',\n",
|
||||
")\n",
|
||||
"fig.show()\n",
|
||||
"\n",
|
||||
"# Plot the cumulative distribution of the elapsed time until first peer connection\n",
|
||||
"# Color the lines based on the number of concurrent sessions and add a legend\n",
|
||||
"fig = px.ecdf(df, x='Elapsed', color='ConcurrentSessions')\n",
|
||||
"fig.update_layout(\n",
|
||||
" title='Cumulative distribution of elapsed time until first peer connection',\n",
|
||||
" xaxis_title='Elapsed time (s)',\n",
|
||||
" yaxis_title='Cumulative probability',\n",
|
||||
" legend_title='Had concurrent sessions',\n",
|
||||
")\n",
|
||||
"fig.show()\n",
|
||||
"\n",
|
||||
"# Plot the histogram of the number of concurrent sessions\n",
|
||||
"fig = px.histogram(df, x='ConcurrentSessions', histnorm='percent')\n",
|
||||
"fig.update_layout(\n",
|
||||
" title='Number of concurrent sessions',\n",
|
||||
" xaxis_title='Had concurrent sessions',\n",
|
||||
" yaxis_title='Percentage',\n",
|
||||
")\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot the histogram of the elapsed time until first peer connection using seaborn\n",
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"sns.histplot(df, x='Elapsed', hue='ConcurrentSessions', multiple='stack', bins=100)\n",
|
||||
"plt.title('Elapsed time until first peer connection')\n",
|
||||
"plt.xlabel('Elapsed time (s)')\n",
|
||||
"plt.ylabel('Count')\n",
|
||||
"plt.legend(title='Had concurrent sessions', labels=['True', 'False'])\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"# Plot the line chart of the elapsed time until first peer connection using seaborn\n",
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"sns.lineplot(data=df, x=df.index, y='Elapsed', marker='o')\n",
|
||||
"plt.title('Elapsed time until first peer connection')\n",
|
||||
"plt.xlabel('Session index')\n",
|
||||
"plt.ylabel('Elapsed time (s)')\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"# Plot the cumulative distribution of the elapsed time until first peer connection using seaborn\n",
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"sns.ecdfplot(df, x='Elapsed', hue='ConcurrentSessions')\n",
|
||||
"plt.title('Cumulative distribution of elapsed time until first peer connection')\n",
|
||||
"plt.xlabel('Elapsed time (s)')\n",
|
||||
"plt.ylabel('Cumulative probability')\n",
|
||||
"plt.legend(title='Had concurrent sessions', labels=['True', 'False'])\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"# Plot the histogram of the number of concurrent sessions using seaborn\n",
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"sns.histplot(df, x='ConcurrentSessions', stat='percent', discrete=True)\n",
|
||||
"plt.title('Number of concurrent sessions')\n",
|
||||
"plt.xlabel('Had concurrent sessions')\n",
|
||||
"plt.ylabel('Percentage')\n",
|
||||
"plt.legend(title='Had concurrent sessions', labels=['True', 'False'])\n",
|
||||
"plt.show()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
[[processors.dedup]]
|
||||
dedup_interval = "600s"
|
||||
|
||||
[[inputs.socket_listener]]
|
||||
service_address = "udp://:8094"
|
||||
data_format = "xpath_json"
|
||||
[[inputs.socket_listener.xpath]]
|
||||
metric_name = "'peertube'"
|
||||
metric_selection = "/*"
|
||||
|
||||
timestamp = "timestamp"
|
||||
timestamp_format = "unix_ms"
|
||||
|
||||
[inputs.socket_listener.xpath.tags]
|
||||
url = "url"
|
||||
session = "session"
|
||||
#id = ??
|
||||
#state = ??
|
||||
|
||||
[inputs.socket_listener.xpath.fields]
|
||||
player = "player"
|
||||
peers = "peers"
|
||||
|
||||
[[outputs.file]]
|
||||
files = ["stdout"]
|
||||
data_format = "json"
|
||||
|
||||
[[outputs.mongodb]]
|
||||
dsn = "mongodb://stats_user:%40z%5EVFhN7q%25vzit@192.168.86.120:27017/?authSource=statistics"
|
||||
database = "statistics"
|
||||
granularity = "seconds"
|
||||
|
||||
# docker run --rm -v .\peertube\statnerd\telegraf.conf:/etc/telegraf/telegraf.conf:ro -p 8094:8094/udp telegraf
|
@@ -1,43 +0,0 @@
|
||||
import logging
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
"""Colored formatter for the logging package."""
|
||||
|
||||
def __init__(
|
||||
self, fmt=None, datefmt=None, style="%", validate=True, *, defaults=None
|
||||
):
|
||||
"""Colored formatter for the logging package."""
|
||||
fmt = fmt or "%(levelname)s: %(message)s"
|
||||
super().__init__(fmt, datefmt, style, validate, defaults=defaults)
|
||||
colors = {
|
||||
"red": "\x1b[31;20m",
|
||||
"bold_red": "\x1b[31;1m",
|
||||
"green": "\x1b[32;20m",
|
||||
"bold_green": "\x1b[32;1m",
|
||||
"yellow": "\x1b[33;20m",
|
||||
"bold_yellow": "\x1b[33;1m",
|
||||
"blue": "\x1b[34;20m",
|
||||
"bold_blue": "\x1b[34;1m",
|
||||
"grey": "\x1b[37;20m",
|
||||
"bold_grey": "\x1b[37;1m",
|
||||
"reset": "\x1b[0m",
|
||||
}
|
||||
self._default_formatter = logging.Formatter(fmt)
|
||||
self._formatters = {
|
||||
100: logging.Formatter(colors["bold_blue"] + fmt + colors["reset"]),
|
||||
logging.DEBUG: logging.Formatter(colors["grey"] + fmt + colors["reset"]),
|
||||
logging.INFO: logging.Formatter(colors["green"] + fmt + colors["reset"]),
|
||||
logging.WARNING: logging.Formatter(
|
||||
colors["yellow"] + fmt + colors["reset"]
|
||||
),
|
||||
logging.ERROR: logging.Formatter(colors["red"] + fmt + colors["reset"]),
|
||||
logging.CRITICAL: logging.Formatter(
|
||||
colors["bold_red"] + fmt + colors["reset"]
|
||||
),
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
"""Override of logging.Formatter.format"""
|
||||
return self._formatters.get(record.levelno, self._default_formatter).format(
|
||||
record
|
||||
)
|
@@ -1,24 +0,0 @@
|
||||
import json
|
||||
import logging
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def __init__(self, custom_func, driver, logger, *args, **kwargs):
|
||||
self._custom_func = custom_func
|
||||
self.logger = logger
|
||||
self.driver = driver
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == '/webrtc-internals-exporter':
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length)
|
||||
self.logger.log(logging.DEBUG, f"POST request,\nPath: {self.path}\nHeaders:\n{self.headers}\n\nBody:\n{post_data.decode('utf-8')}")
|
||||
self._custom_func(self.driver, json.loads(post_data.decode('utf-8')))
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'POST request received')
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'404 Not Found')
|
@@ -1,37 +0,0 @@
|
||||
# WebRTC Internals Exporter
|
||||
A Chromium browser extension that allows to collect WebRTC stats and export them to a Prometheus PushGateway service.
|
||||
|
||||
## Install
|
||||
|
||||
### Using the Chrome Web Store
|
||||
[Link](https://chromewebstore.google.com/detail/webrtc-internals-exporter/jbgkajlogkmfemdjhiiicelanbipacpa)
|
||||
|
||||
### Using the packed extension
|
||||
Download the `.crx` file from the [releases page](https://github.com/vpalmisano/webrtc-internals-exporter/releases) and drop it
|
||||
into the [chrome://extensions/](chrome://extensions/) page.
|
||||
Alternatively, you can download a `.zip` or `tar.gz` file from the releases page
|
||||
and load the decompressed folder as an unpacked extension.
|
||||
|
||||
Ref. https://developer.chrome.com/docs/extensions/mv3/hosting/
|
||||
|
||||
### From sources
|
||||
Run the `./build.sh` script and load the `build` folder as an unpacked extension
|
||||
in your Chromium browser after enabling the developer mode.
|
||||
|
||||
## Usage
|
||||
1. Visit the extension options page, set the PushGateway URL and, optionally, the username and password.
|
||||
2. Load the page where you want to collect the stats and click on the extension icon to enable the stats collection on that URL (disabled by default).
|
||||
3. The stats will be collected and sent to the PushGateway service. You can use the provided [Grafana dashboard](https://github.com/vpalmisano/webrtc-internals-exporter/tree/main/grafana) to visualize them.
|
||||
|
||||
## Debugging
|
||||
The extension logs are available in the browser console after setting:
|
||||
```js
|
||||
localStorage.setItem("webrtc-internal-exporter:debug", "true")
|
||||
```
|
||||
|
||||
The running PeerConnections objects can be manually inspected using the following
|
||||
command in the browser console:
|
||||
```js
|
||||
> webrtcInternalExporter.peerConnections
|
||||
Map(1) {'b03c3616-3f91-42b5-85df-7dbebefae8bd' => RTCPeerConnection}
|
||||
```
|
@@ -1 +0,0 @@
|
||||
.version[data-v-4cce5501]{text-decoration:none}
|
@@ -1 +0,0 @@
|
||||
import{_ as y,a as O,h as o,j as c,k as _,l as a,m as t,x,q as w,t as k,z as V}from"./_plugin-vue_export-helper-deb87276.js";const C={class:"version",href:"https://github.com/vpalmisano/webrtc-internals-exporter",target:"_blank",title:"Homepage"},B={__name:"Popup",setup(N){var i;const e=O({version:"0.1.9",error:"",info:"",enabled:!1,origin:"",enabledOrigins:{}});async function d(){const{enabledOrigins:n}=await chrome.storage.sync.get("enabledOrigins");e.enabledOrigins=n,e.enabled=!!e.enabledOrigins[e.origin]}async function p(n){if(console.log("saveOptions",e.origin,n),n)e.enabledOrigins={...e.enabledOrigins,[e.origin]:n};else{const r={...e.enabledOrigins};delete r[e.origin],e.enabledOrigins=r}chrome.storage&&await chrome.storage.sync.set({enabledOrigins:e.enabledOrigins})}return(i=chrome.tabs)==null||i.query({active:!0,currentWindow:!0}).then(n=>{const r=n[0];return chrome.scripting.executeScript({target:{tabId:r.id},function:()=>window.location.origin})}).then(n=>{e.origin=n[0].result}).then(()=>d()).catch(n=>{console.error("Load options error:",n),e.error=`Load options error: ${n.message}`}),(n,r)=>{const u=o("v-app-bar"),g=o("v-alert"),m=o("v-checkbox"),s=o("v-col"),l=o("v-row"),b=o("v-container"),v=o("v-main"),f=o("v-layout");return c(),_(f,null,{default:a(()=>[t(u,{title:"WebRTC Internals Exporter",color:"primary",density:"compact"}),t(v,{class:"d-flex align-center justify-left",style:{"min-width":"20rem"}},{default:a(()=>[t(b,null,{default:a(()=>[t(l,null,{default:a(()=>[t(s,{cols:"12",md:"12"},{default:a(()=>[e.error.length>0?(c(),_(g,{key:0,text:e.error,type:"error"},null,8,["text"])):x("",!0),t(m,{color:"indigo",modelValue:e.enabled,"onUpdate:modelValue":[r[0]||(r[0]=h=>e.enabled=h),p],label:"Enable for "+e.origin,"hide-details":""},null,8,["modelValue","label"])]),_:1})]),_:1}),t(l,null,{default:a(()=>[t(s,{cols:"12",md:"12"},{default:a(()=>[w("a",C,"v"+k(e.version),1)]),_:1})]),_:1})]),_:1})]),_:1})]),_:1})}}},I=y(B,[["__scopeId","data-v-16634bc6"]]);V(I);
|
@@ -1 +0,0 @@
|
||||
.version[data-v-16634bc6]{font-size:smaller;text-decoration:none}
|
@@ -1,373 +0,0 @@
|
||||
/* global chrome, pako */
|
||||
|
||||
function log(...args) {
|
||||
console.log.apply(null, ["[webrtc-internal-exporter:background]", ...args]);
|
||||
}
|
||||
|
||||
log("loaded");
|
||||
|
||||
import "/assets/pako.min.js";
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
url: "http://localhost:9092",
|
||||
username: "",
|
||||
password: "",
|
||||
updateInterval: 2,
|
||||
gzip: false,
|
||||
job: "webrtc-internals-exporter",
|
||||
enabledOrigins: {
|
||||
"https://tube.kobim.cloud": true,
|
||||
},
|
||||
enabledStats: ["data-channel", "local-candidate", "remote-candidate"]
|
||||
};
|
||||
|
||||
const options = {};
|
||||
|
||||
// Handle install/update.
|
||||
chrome.runtime.onInstalled.addListener(async ({ reason }) => {
|
||||
log("onInstalled", reason);
|
||||
if (reason === "install") {
|
||||
await chrome.storage.sync.set(DEFAULT_OPTIONS);
|
||||
} else if (reason === "update") {
|
||||
const options = await chrome.storage.sync.get();
|
||||
await chrome.storage.sync.set({
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
await chrome.alarms.create("webrtc-internals-exporter-alarm", {
|
||||
delayInMinutes: 1,
|
||||
periodInMinutes: 1,
|
||||
});
|
||||
});
|
||||
|
||||
async function updateTabInfo(tab) {
|
||||
const tabId = tab.id;
|
||||
const origin = new URL(tab.url || tab.pendingUrl).origin;
|
||||
|
||||
if (options.enabledOrigins && options.enabledOrigins[origin] === true) {
|
||||
const { peerConnectionsPerOrigin } = await chrome.storage.local.get(
|
||||
"peerConnectionsPerOrigin",
|
||||
);
|
||||
const peerConnections =
|
||||
(peerConnectionsPerOrigin && peerConnectionsPerOrigin[origin]) || 0;
|
||||
|
||||
chrome.action.setTitle({
|
||||
title: `WebRTC Internals Exporter\nActive Peer Connections: ${peerConnections}`,
|
||||
tabId,
|
||||
});
|
||||
chrome.action.setBadgeText({ text: `${peerConnections}`, tabId });
|
||||
chrome.action.setBadgeBackgroundColor({ color: "rgb(63, 81, 181)", tabId });
|
||||
} else {
|
||||
chrome.action.setTitle({
|
||||
title: `WebRTC Internals Exporter (disabled)`,
|
||||
tabId,
|
||||
});
|
||||
chrome.action.setBadgeText({ text: "", tabId });
|
||||
}
|
||||
}
|
||||
|
||||
async function optionsUpdated() {
|
||||
const [tab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
lastFocusedWindow: true,
|
||||
});
|
||||
await updateTabInfo(tab);
|
||||
}
|
||||
|
||||
chrome.storage.sync.get().then((ret) => {
|
||||
Object.assign(options, ret);
|
||||
log("options loaded");
|
||||
optionsUpdated();
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||
if (areaName !== "sync") return;
|
||||
|
||||
for (let [key, { newValue }] of Object.entries(changes)) {
|
||||
options[key] = newValue;
|
||||
}
|
||||
log("options changed");
|
||||
optionsUpdated();
|
||||
});
|
||||
|
||||
chrome.tabs.onActivated.addListener(async ({ tabId }) => {
|
||||
try {
|
||||
const tab = await chrome.tabs.get(tabId);
|
||||
await updateTabInfo(tab);
|
||||
} catch (err) {
|
||||
log(`get tab error: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
|
||||
if (!changeInfo.url) return;
|
||||
await updateTabInfo({ id: tabId, url: changeInfo.url });
|
||||
});
|
||||
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === "webrtc-internals-exporter-alarm") {
|
||||
cleanupPeerConnections().catch((err) => {
|
||||
log(`cleanup peer connections error: ${err.message}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function setPeerConnectionLastUpdate({ id, origin }, lastUpdate = 0) {
|
||||
let { peerConnectionsLastUpdate } = await chrome.storage.local.get(
|
||||
"peerConnectionsLastUpdate",
|
||||
);
|
||||
if (!peerConnectionsLastUpdate) {
|
||||
peerConnectionsLastUpdate = {};
|
||||
}
|
||||
if (lastUpdate) {
|
||||
peerConnectionsLastUpdate[id] = { origin, lastUpdate };
|
||||
} else {
|
||||
delete peerConnectionsLastUpdate[id];
|
||||
}
|
||||
await chrome.storage.local.set({ peerConnectionsLastUpdate });
|
||||
|
||||
const peerConnectionsPerOrigin = {};
|
||||
Object.values(peerConnectionsLastUpdate).forEach(({ origin: o }) => {
|
||||
if (!peerConnectionsPerOrigin[o]) {
|
||||
peerConnectionsPerOrigin[o] = 0;
|
||||
}
|
||||
peerConnectionsPerOrigin[o]++;
|
||||
});
|
||||
await chrome.storage.local.set({ peerConnectionsPerOrigin });
|
||||
await optionsUpdated();
|
||||
}
|
||||
|
||||
async function cleanupPeerConnections() {
|
||||
let { peerConnectionsLastUpdate } = await chrome.storage.local.get(
|
||||
"peerConnectionsLastUpdate",
|
||||
);
|
||||
if (
|
||||
!peerConnectionsLastUpdate ||
|
||||
!Object.keys(peerConnectionsLastUpdate).length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
`checking stale peer connections (${
|
||||
Object.keys(peerConnectionsLastUpdate).length
|
||||
} total)`,
|
||||
);
|
||||
const now = Date.now();
|
||||
await Promise.allSettled(
|
||||
Object.entries(peerConnectionsLastUpdate)
|
||||
.map(([id, { origin, lastUpdate }]) => {
|
||||
if (
|
||||
now - lastUpdate >
|
||||
Math.max(2 * options.updateInterval, 30) * 1000
|
||||
) {
|
||||
return { id, origin };
|
||||
}
|
||||
})
|
||||
.filter((ret) => !!ret?.id)
|
||||
.map(({ id, origin }) => {
|
||||
log(`removing stale peer connection metrics: ${id} ${origin}`);
|
||||
return sendData("DELETE", { id, origin });
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Send data to pushgateway.
|
||||
async function sendData(method, { id, origin }, data) {
|
||||
const { url, username, password, gzip, job } = options;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
if (username && password) {
|
||||
headers.Authorization = "Basic " + btoa(`${username}:${password}`);
|
||||
}
|
||||
if (data && gzip) {
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
data = await pako.gzip(data);
|
||||
}
|
||||
log(`sendData: ${data} \n ${data.length} bytes (gzip: ${gzip}) url: ${url} job: ${job}`);
|
||||
const start = Date.now();
|
||||
const response = await fetch(
|
||||
`${url}/metrics/job/${job}/peerConnectionId/${id}`,
|
||||
{
|
||||
method,
|
||||
headers,
|
||||
body: method === "POST" ? data : undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const stats = await chrome.storage.local.get([
|
||||
"messagesSent",
|
||||
"bytesSent",
|
||||
"totalTime",
|
||||
"errors",
|
||||
]);
|
||||
if (data) {
|
||||
stats.messagesSent = (stats.messagesSent || 0) + 1;
|
||||
stats.bytesSent = (stats.bytesSent || 0) + data.length;
|
||||
stats.totalTime = (stats.totalTime || 0) + Date.now() - start;
|
||||
}
|
||||
if (!response.ok) {
|
||||
stats.errors = (stats.errors || 0) + 1;
|
||||
}
|
||||
await chrome.storage.local.set(stats);
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Response status: ${response.status} error: ${text}`);
|
||||
}
|
||||
|
||||
await setPeerConnectionLastUpdate(
|
||||
{ id, origin },
|
||||
method === "POST" ? start : undefined,
|
||||
);
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
async function sendJsonData(method, { id, origin }, data) {
|
||||
const { url, username, password, gzip, job } = options;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
if (username && password) {
|
||||
headers.Authorization = "Basic " + btoa(`${username}:${password}`);
|
||||
}
|
||||
if (data && gzip) {
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
data = await pako.gzip(data);
|
||||
}
|
||||
log(`sendData: ${data} \n ${data.length} bytes (gzip: ${gzip}) url: ${url} job: ${job}`);
|
||||
const start = Date.now();
|
||||
const response = await fetch(
|
||||
`${url}/${job}`,
|
||||
{
|
||||
method,
|
||||
headers,
|
||||
body: method === "POST" ? data : undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const stats = await chrome.storage.local.get([
|
||||
"messagesSent",
|
||||
"bytesSent",
|
||||
"totalTime",
|
||||
"errors",
|
||||
]);
|
||||
if (data) {
|
||||
stats.messagesSent = (stats.messagesSent || 0) + 1;
|
||||
stats.bytesSent = (stats.bytesSent || 0) + data.length;
|
||||
stats.totalTime = (stats.totalTime || 0) + Date.now() - start;
|
||||
}
|
||||
if (!response.ok) {
|
||||
stats.errors = (stats.errors || 0) + 1;
|
||||
}
|
||||
await chrome.storage.local.set(stats);
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`Response status: ${response.status} error: ${text}`);
|
||||
}
|
||||
|
||||
await setPeerConnectionLastUpdate(
|
||||
{ id, origin },
|
||||
method === "POST" ? start : undefined,
|
||||
);
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
const QualityLimitationReasons = {
|
||||
none: 0,
|
||||
bandwidth: 1,
|
||||
cpu: 2,
|
||||
other: 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* sendPeerConnectionStats
|
||||
* @param {string} url
|
||||
* @param {string} id
|
||||
* @param {RTCPeerConnectionState} state
|
||||
* @param {any} values
|
||||
*/
|
||||
async function sendPeerConnectionStats(url, id, state, values) {
|
||||
const origin = new URL(url).origin;
|
||||
|
||||
if (state === "closed") {
|
||||
return sendData("DELETE", { id, origin });
|
||||
}
|
||||
|
||||
let data = "";
|
||||
const sentTypes = new Set();
|
||||
|
||||
values.forEach((value) => {
|
||||
const type = value.type.replace(/-/g, "_");
|
||||
const labels = [`pageUrl="${url}"`];
|
||||
const metrics = [];
|
||||
|
||||
if (value.type === "peer-connection") {
|
||||
labels.push(`state="${state}"`);
|
||||
}
|
||||
|
||||
Object.entries(value).forEach(([key, v]) => {
|
||||
if (typeof v === "number") {
|
||||
metrics.push([key, v]);
|
||||
} else if (typeof v === "object") {
|
||||
Object.entries(v).forEach(([subkey, subv]) => {
|
||||
if (typeof subv === "number") {
|
||||
metrics.push([`${key}_${subkey}`, subv]);
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
key === "qualityLimitationReason" &&
|
||||
QualityLimitationReasons[v] !== undefined
|
||||
) {
|
||||
metrics.push([key, QualityLimitationReasons[v]]);
|
||||
} else if (key === "googTimingFrameInfo") {
|
||||
// TODO
|
||||
} else {
|
||||
labels.push(`${key}="${v}"`);
|
||||
}
|
||||
});
|
||||
|
||||
metrics.forEach(([key, v]) => {
|
||||
const name = `${type}_${key.replace(/-/g, "_")}`;
|
||||
let typeDesc = "";
|
||||
|
||||
if (!sentTypes.has(name)) {
|
||||
typeDesc = `# TYPE ${name} gauge\n`;
|
||||
sentTypes.add(name);
|
||||
}
|
||||
data += `${typeDesc}${name}{${labels.join(",")}} ${v}\n`;
|
||||
});
|
||||
});
|
||||
|
||||
if (data.length > 0) {
|
||||
return sendData("POST", { id, origin }, data + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.event === "peer-connection-stats") {
|
||||
const { url, id, state, values } = message.data;
|
||||
|
||||
sendData("POST", { id, origin: new URL(url).origin }, JSON.stringify(message.data))
|
||||
.then(() => {
|
||||
sendResponse({});
|
||||
})
|
||||
.catch((err) => {
|
||||
sendResponse({ error: err.message });
|
||||
});
|
||||
} else if (message.event === "peer-connections-stats") {
|
||||
const { stats } = message.data;
|
||||
|
||||
sendJsonData("POST", { id: "all", origin: "all" }, JSON.stringify(message.data))
|
||||
} else {
|
||||
sendResponse({ error: "unknown event" });
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
@@ -1,123 +0,0 @@
|
||||
/* global chrome */
|
||||
|
||||
if (window.location.protocol.startsWith("http")) {
|
||||
const log = (...args) => {
|
||||
try {
|
||||
if (localStorage.getItem("webrtc-internal-exporter:debug") === "true") {
|
||||
console.log.apply(null, [
|
||||
"[webrtc-internal-exporter:content-script]",
|
||||
...args,
|
||||
]);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore localStorage errors.
|
||||
}
|
||||
};
|
||||
|
||||
const injectScript = (file_path) => {
|
||||
const head = document.querySelector("head");
|
||||
const script = document.createElement("script");
|
||||
script.setAttribute("type", "text/javascript");
|
||||
script.setAttribute("src", file_path);
|
||||
head.appendChild(script);
|
||||
};
|
||||
|
||||
setTimeout(() => injectScript(chrome.runtime.getURL("override.js")));
|
||||
|
||||
// Handle options.
|
||||
const options = {
|
||||
url: "",
|
||||
enabled: false,
|
||||
updateInterval: 2000,
|
||||
enabledStats: [],
|
||||
};
|
||||
|
||||
const sendOptions = () => {
|
||||
window.postMessage({
|
||||
event: "webrtc-internal-exporter:options",
|
||||
options,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
chrome.storage.sync
|
||||
.get(["url", "enabledOrigins", "updateInterval", "enabledStats"])
|
||||
.then((ret) => {
|
||||
log(`options loaded:`, ret);
|
||||
options.url = ret.url || "";
|
||||
options.enabled =
|
||||
ret.enabledOrigins &&
|
||||
ret.enabledOrigins[window.location.origin] === true;
|
||||
options.updateInterval = (ret.updateInterval || 2) * 1000;
|
||||
options.enabledStats = Object.values(ret.enabledStats || {});
|
||||
sendOptions();
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, area) => {
|
||||
if (area !== "sync") return;
|
||||
|
||||
let changed = false;
|
||||
for (let [key, { newValue }] of Object.entries(changes)) {
|
||||
if (key === "url") {
|
||||
options.url = newValue;
|
||||
changed = true;
|
||||
} else if (key === "enabledOrigins") {
|
||||
options.enabled = newValue[window.location.origin] === true;
|
||||
changed = true;
|
||||
} else if (key === "updateInterval") {
|
||||
options.updateInterval = newValue * 1000;
|
||||
changed = true;
|
||||
} else if (key === "enabledStats") {
|
||||
options.enabledStats = Object.values(newValue);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
log(`options changed:`, options);
|
||||
sendOptions();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle stats messages.
|
||||
window.addEventListener("message", async (message) => {
|
||||
const { event, url, id, state, values, stats } = message.data;
|
||||
if (event === "webrtc-internal-exporter:ready") {
|
||||
sendOptions();
|
||||
} else if (event === "webrtc-internal-exporter:peer-connection-stats") {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
event: "peer-connection-stats",
|
||||
data: {
|
||||
url,
|
||||
id,
|
||||
state,
|
||||
values,
|
||||
},
|
||||
});
|
||||
if (response.error) {
|
||||
log(`error: ${response.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
log(`error: ${error.message}`);
|
||||
}
|
||||
} else if (event === "webrtc-internal-exporter:peer-connections-stats") {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
event: "peer-connections-stats",
|
||||
data: stats,
|
||||
});
|
||||
if (response.error) {
|
||||
log(`error: ${response.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
log(`error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[webrtc-internal-exporter:content-script] error: ${error.message}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 718 B |
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"name": "WebRTC Internals Exporter",
|
||||
"description": "WebRTC Internals Exporter",
|
||||
"author": "Vittorio Palmisano",
|
||||
"version": "0.1.9",
|
||||
"manifest_version": 3,
|
||||
"icons": {
|
||||
"16": "images/icon16.png",
|
||||
"48": "images/icon48.png",
|
||||
"128": "images/icon128.png"
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"activeTab",
|
||||
"tabs",
|
||||
"scripting",
|
||||
"alarms"
|
||||
],
|
||||
"host_permissions": [],
|
||||
"action": {
|
||||
"default_title": "WebRTC Internals Exporter",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options.html",
|
||||
"open_in_tab": true
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"https://*/*",
|
||||
"http://*/*"
|
||||
],
|
||||
"js": [
|
||||
"content-script.js"
|
||||
],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true,
|
||||
"match_about_blank": true
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"override.js"
|
||||
],
|
||||
"matches": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebRTC Internals Exporter</title>
|
||||
<script type="module" crossorigin src="/assets/options-8c2aaa1b.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-deb87276.js">
|
||||
<link rel="stylesheet" href="/assets/_plugin-vue_export-helper.css">
|
||||
<link rel="stylesheet" href="/assets/options.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
@@ -1,135 +0,0 @@
|
||||
function log(...args) {
|
||||
console.log.apply(null, ["[webrtc-internal-exporter:override]", ...args]);
|
||||
}
|
||||
|
||||
log("Override RTCPeerConnection.");
|
||||
|
||||
class WebrtcInternalExporter {
|
||||
peerConnections = new Map();
|
||||
|
||||
url = "";
|
||||
enabled = false;
|
||||
updateInterval = 2000;
|
||||
enabledStats = [];
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("message", async (message) => {
|
||||
const { event, options } = message.data;
|
||||
if (event === "webrtc-internal-exporter:options") {
|
||||
log("options updated:", options);
|
||||
Object.assign(this, options);
|
||||
}
|
||||
});
|
||||
|
||||
window.postMessage({ event: "webrtc-internal-exporter:ready" });
|
||||
this.collectAllStats();
|
||||
}
|
||||
|
||||
randomId() {
|
||||
return (
|
||||
window.crypto?.randomUUID() || (2 ** 64 * Math.random()).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
add(pc) {
|
||||
const id = this.randomId();
|
||||
this.peerConnections.set(id, pc);
|
||||
pc.addEventListener("connectionstatechange", () => {
|
||||
if (pc.connectionState === "closed") {
|
||||
this.peerConnections.delete(id);
|
||||
}
|
||||
});
|
||||
//this.collectAndPostStats(id);
|
||||
}
|
||||
|
||||
async collectAndPostSingleStat(id) {
|
||||
const stats = await this.collectStats(id, this.collectAndPostSingleStat);
|
||||
if (Object.keys(stats).length === 0 || !stats) return;
|
||||
|
||||
window.postMessage(
|
||||
{
|
||||
event: "webrtc-internal-exporter:peer-connection-stats",
|
||||
...stats,
|
||||
},
|
||||
[stats],
|
||||
);
|
||||
}
|
||||
|
||||
async collectAllStats() {
|
||||
const stats = [];
|
||||
|
||||
for (const [id, pc] of this.peerConnections) {
|
||||
if (this.url && this.enabled && pc.connectionState === "connected") {
|
||||
const pcStats = await this.collectStats(id, pc);
|
||||
stats.push(pcStats);
|
||||
}
|
||||
}
|
||||
|
||||
//if (stats.length !== 0) {
|
||||
window.postMessage(
|
||||
{
|
||||
event: "webrtc-internal-exporter:peer-connections-stats",
|
||||
stats,
|
||||
},
|
||||
[stats],
|
||||
);
|
||||
|
||||
log(`Stats collected:`, stats);
|
||||
//}
|
||||
|
||||
setTimeout(this.collectAllStats.bind(this), this.updateInterval);
|
||||
return stats;
|
||||
}
|
||||
|
||||
async collectStats(id, pc, binding) {
|
||||
var completeStats = {};
|
||||
|
||||
if (!pc) {
|
||||
pc = this.peerConnections.get(id);
|
||||
if (!pc) return;
|
||||
}
|
||||
|
||||
if (this.url && this.enabled && pc.connectionState === "connected") {
|
||||
try {
|
||||
const stats = await pc.getStats();
|
||||
const values = [...stats.values()].filter(
|
||||
(v) =>
|
||||
["peer-connection", ...this.enabledStats].indexOf(v.type) !== -1
|
||||
);
|
||||
|
||||
completeStats = {
|
||||
url: window.location.href,
|
||||
id,
|
||||
state: pc.connectionState,
|
||||
values,
|
||||
};
|
||||
} catch (error) {
|
||||
log(`collectStats error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (pc.connectionState === "closed") {
|
||||
this.peerConnections.delete(id);
|
||||
} else {
|
||||
if (binding) {
|
||||
setTimeout(binding.bind(this), this.updateInterval, id);
|
||||
}
|
||||
}
|
||||
|
||||
return completeStats;
|
||||
}
|
||||
}
|
||||
|
||||
const webrtcInternalExporter = new WebrtcInternalExporter();
|
||||
|
||||
window.RTCPeerConnection = new Proxy(window.RTCPeerConnection, {
|
||||
construct(target, argumentsList) {
|
||||
log(`RTCPeerConnection`, argumentsList);
|
||||
|
||||
const pc = new target(...argumentsList);
|
||||
|
||||
webrtcInternalExporter.add(pc);
|
||||
|
||||
return pc;
|
||||
},
|
||||
});
|
@@ -1,16 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebRTC Internals Exporter</title>
|
||||
<script type="module" crossorigin src="/assets/popup-7cc154e5.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-deb87276.js">
|
||||
<link rel="stylesheet" href="/assets/_plugin-vue_export-helper.css">
|
||||
<link rel="stylesheet" href="/assets/popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|