# include " wtr/watcher.hpp "
# include < iostream >
# include < string >
using namespace std ;
using namespace wtr ;
// The event type, and every field within it, has
// string conversions and stream operators. All
// kinds of strings -- Narrow, wide and weird ones.
// If we don't want particular formatting, we can
// json-serialize and show the event like this:
// some_stream << event
// Here, we'll apply our own formatting.
auto show (event e) {
cout << to<string>(e. effect_type ) + ' '
+ to<string>(e. path_type ) + ' '
+ to<string>(e. path_name )
+ (e. associated ? " -> " + to<string>(e. associated -> path_name ) : " " )
<< endl;
}
auto main () -> int {
// Watch the current directory asynchronously,
// calling the provided function on each event.
auto watcher = watch ( " . " , show);
// Do some work. (We'll just wait for a newline.)
getchar ();
// The watcher would close itself around here,
// though we can check and close it ourselves.
return watcher. close () ? 0 : 1 ;
} # Sigh
PLATFORM_EXTRAS= $( test " $( uname ) " = Darwin && echo ' -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -framework CoreFoundation -framework CoreServices ' )
# Build
eval c++ -std=c++17 -Iinclude src/wtr/tiny_watcher/main.cpp -o watcher $PLATFORM_EXTRAS
# Run
./watcher #include "wtr/watcher-c.h"
#include <stdio.h>
void callback ( struct wtr_watcher_event event , void * _ctx ) {
printf (
"path name: %s, effect type: %d path type: %d, effect time: %lld, associated path name: %sn" ,
event . path_name ,
event . effect_type ,
event . path_type ,
event . effect_time ,
event . associated_path_name ? event . associated_path_name : ""
);
}
int main () {
void * watcher = wtr_watcher_open ( "." , callback , NULL );
getchar ();
return ! wtr_watcher_close ( watcher );
}pip install wtr-watcher from watcher import Watch
with Watch ( "." , print ):
input ()cargo add wtr-watcher tokio futures use futures :: StreamExt ;
use wtr_watcher :: Watch ;
# [ tokio :: main ( flavor = "current_thread" ) ]
async fn main ( ) -> Result < ( ) , Box < dyn std :: error :: Error > > {
let show = |e| async move { println ! ( "{e:?}" ) } ;
let events = Watch :: try_new ( "." ) ? ;
events . for_each ( show ) . await ;
Ok ( ( ) )
} import * as watcher from 'watcher' ;
var w = watcher . watch ( '.' , ( event ) => {
console . log ( event ) ;
} ) ;
process . stdin . on ( 'data' , ( ) => {
w . close ( ) ;
process . exit ( ) ;
} ) ;La sortie de chacun ci-dessus sera quelque chose que cela, selon le format:
modify file /home/e-dant/dev/watcher/.git/refs/heads/next.lock
rename file /home/e-dant/dev/watcher/.git/refs/heads/next.lock -> /home/e-dant/dev/watcher/.git/refs/heads/next
create file /home/e-dant/dev/watcher/.git/HEAD.lock
Apprécier!
Un observateur d'événements de système de fichiers qui est
J'essaie de garder les lignes 1579 qui composent l'exécution de Watcher relativement simple et l'API pratique:
auto w = watch(path, [](event ev) { cout << ev; });wtr.watcher ~Watcher peut être utilisé comme bibliothèque, un programme ou les deux . Si vous ne cherchez pas à créer quelque chose avec la bibliothèque, pas de soucis. Utilisez simplement le nôtre et vous avez un observateur de systèmes de fichiers qui imprime les événements du système de fichiers en tant que JSON. Soigné. Voici comment:
# The main branch is the (latest) release branch.
git clone https://github.com/e-dant/watcher.git && cd watcher
# Via Nix
nix run | grep -oE ' cmake-is-tough '
# With the build script
tool/build --no-build-test --no-run && cd out/this/Release # Build the release version for the host platform.
./wtr.watcher | grep -oE ' needle-in-a-haystack/.+" ' # Use it, pipe it, whatever. (This is an .exe on Windows.)Vous pouvez regarder un système de fichiers entier avec ce projet. Dans presque tous les cas, nous utilisons une quantité presque nul de ressources et faisons un usage efficace du cache. Nous testons régulièrement que les frais généraux de détection et d'envoi d'un événement à l'utilisateur sont inférieurs à un ordre de grandeur que les opérations du système de fichiers mesurées.
Nous mettons ce projet à travers des tests unitaires contre tous les consacteurs disponibles. Ce code s'efforce d'être du thread, de la mémoire, des limites, du type et de la sécurité des ressources. Ce qui nous manque de la langue, nous essayons de compenser les tests. Pour une définition pratique de la sécurité, ce projet correspond probablement.
Watcher dépend de la bibliothèque standard C ++. Pour plus d'efficacité, nous tirons parti du système d'exploitation lorsque cela est possible sur Linux, Darwin et Windows. Pour les tests et le débogage, nous utilisons le snitch et les désinfectants.
L'observateur est relâchable presque partout. La seule exigence est un système de fichiers.
Les pièces importantes sont la bibliothèque (d'en-tête uniquement) et le programme CLI (facultatif).
include/wtr/watcher.hpp . Incluez cela pour utiliser Watcher dans votre projet C ++. La copie dans votre projet, et l'inclure en tant que #include "wtr/watcher.hpp" (ou similaire) est suffisante pour être opérationnel dans cette langue. Des documents supplémentaires et des internes de bibliothèque de haut niveau se trouvent dans l'événement et les en-têtes de montée.watcher-c . Construisez ceci pour utiliser Watcher à partir de C ou via un FFI dans d'autres langues.src/wtr/watcher/main.cpp . Construisez-le pour utiliser Watcher à partir de la ligne de commande. La sortie est un flux JSON exhaustif.src/wtr/tiny_watcher/main.cpp . Un programme CLI très minimal et plus lisible par l'homme. La source pour cela est presque identique à l'exemple d'utilisation de C ++.Un arbre d'annuaire est dans les notes ci-dessous.
Les deux blocs de construction fondamentaux ici sont:
watch ou la classe (selon la langue)event (ou nommé de la même manière, à nouveau en fonction de la langue) watch prend un chemin, ce qui est une chose en forme de chaîne, et un rappel, avec est une chose en forme de fonction. Par exemple, Passing watch A Character Back et une fermeture fonctionnerait bien en C ++.
Des exemples pour une variété de langues peuvent être trouvés dans le démarrage rapide. L'API est relativement cohérente entre les langues.
L'observateur continuera volontiers à regarder jusqu'à ce que vous l'arrêtez ou qu'il frappe une erreur irréalisable.
L'objet event est utilisé pour transmettre des informations sur les événements du système de fichiers au rappel donné (par vous) à watch .
L'objet event contiendra:
path_name , qui est un chemin absolu vers l'événement.path_type , le type de chemin. Un des:dirfilehard_linksym_linkwatcherothereffect_type , "Qu'est-ce qui s'est passé". Un des:renamemodifycreatedestroyownerothereffect_time , l'heure de l'événement en nanosecondes depuis l'époque.associated (un événement, C ++) ou associated_path_name (toutes les autres implémentations, un seul nom de chemin):(Notez que, pour JavaScript, nous utilisons le cas de camel, pour être cohérent avec l'écosystème de cette langue.)
Le type watcher est spécial.
Les événements avec ce type comprendront des messages de l'observateur. Vous pouvez recevoir des messages d'erreur ou des mises à jour d'état importantes.
Ce format a été choisi pour prendre en charge les messages asynchrones de l'observateur dans un format générique et portable.
Deux des événements "Watcher" les plus importants sont l'événement initial "en direct" et l'événement "Die" final.
Le message apparaît apparenté sur le chemin de base regardé.
Par exemple, après avoir ouvert un observateur sur /a/path , vous pouvez recevoir ces messages de l'observateur:
s/self/live@/a/pathe/self/die@/a/path Les messages commencent toujours par un s , indiquant une opération réussie, un w , indiquant un avertissement non mortel, ou un e , indiquant une erreur fatale.
Surtout, la fermeture de l'observateur produira toujours une erreur
self/live n'a pas encore été envoyé; Ou, en d'autres termes, si l'observateur n'a pas complètement commencé. Dans ce cas, l'observateur se fermera immédiatement après l'ouverture complètement et signalera une erreur dans tous les appels à fermer. Le dernier événement sera toujours un événement destroy de l'observateur. Vous pouvez l'analyser comme ça, pour un événement ev :
ev.path_type == path_type::watcher && ev.effect_type == effect_type::destroy;Happy Hacking.
Ce projet essaie de vous permettre de travailler facilement avec des événements de système de fichiers. Je pense que les bons outils sont faciles à utiliser. Si ce projet n'est pas ergonomique, déposez un problème.
Voici un instantané de la sortie prise lors de la préparation de cet engagement, juste avant d'écrire ce paragraphe.
{
"1666393024210001000" : {
"path_name" : " /home/edant/dev/watcher/.git/logs/HEAD " ,
"effect_type" : " modify " ,
"path_type" : " file "
},
"1666393024210026000" : {
"path_name" : " /home/edant/dev/watcher/.git/logs/refs/heads/next " ,
"effect_type" : " modify " ,
"path_type" : " file "
},
"1666393024210032000" : {
"path_name" : " /home/edant/dev/watcher/.git/refs/heads/next.lock " ,
"effect_type" : " create " ,
"path_type" : " other "
}
}Ce qui est plutôt cool.
Un programme capable est là.
Ce projet est accessible à travers:
tool/build : comprend l'en-tête / bibliothèque C ++, les cibles de test, de test et de référencetool/cross : comprend la bibliothèque et l'en-tête partagés watcher-c , compilé croisé pour de nombreuses plates-formeswatcher-c (statique et partagée), CLI, test et cibles de référenceVoir le package ici.
nix build # To just build
nix run # Build the default target, then run without arguments
nix run . -- / | jq # Build and run, watch the root directory, pipe it to jq
nix develop # Enter an isolated development shell with everything needed to explore this projectbazel build cli # Build, but don't run, the cli
bazel build hdr # Ditto, for the single-header
bazel run cli # Run the cli program without argumentstool/build
cd out/this/Release
# watches the current directory forever
./wtr.watcher
# watches some path for 10 seconds
./wtr.watcher ' your/favorite/path ' -s 10Cela s'occupera de certains spécifiques de la plate-forme, de la construction des variantes de libération, de débogage et de désinfectant et exécuter certains tests.
cmake -S . -B out
cmake --build out --config Release
cd out
# watches the current directory forever
./wtr.watcher
# watches some path for 10 seconds
./wtr.watcher ' your/favorite/path ' -s 10Les observateurs de toutes les plateformes ignorent intentionnellement les événements de modification qui ne changent que le temps d'access sur un fichier ou un répertoire.
L'utilité de ces événements était discutable.
Cela semblait plus nocif que bien. D'autres observateurs, comme C # Watcher de Microsoft, les ignorent par défaut. Certaines applications utilisateur s'appuient sur des événements de modification pour savoir quand lui-même pour recharger un fichier.
De meilleures solutions plus complètes existent, et ces défauts peuvent à nouveau changer.
Fournir un moyen d'ignorer les événements d'un processus de processus, un raccourci de "ce" processus et un moyen de spécifier quels types de sources d'événements qui nous intéressent sont de bons candidats à des solutions plus complètes.
J'étais à l'aise avec C ++ quand j'ai écrit ceci pour la première fois. J'ai ensuite réécrit ce projet en rouille comme une expérience. Il y a des avantages et des inconvénients à la rouille. Certaines choses étaient un peu plus sûres à exprimer, d'autres ne l'étaient certainement pas. La nécessité de faire des mathématiques de pointeur sur certains types opaques de taille variable du noyau, par exemple, n'est pas plus sûr à exprimer dans la rouille. D'autres choses sont plus sûres, mais ce projet n'en profite pas beaucoup.
La rouille brille vraiment de l'utilisabilité et de l'expression. Cela pourrait être une raison suffisante de l'utiliser. Entre autres choses, nous pourrions travailler avec des traits asynchrones et des types algébriques pour un bon bien.
Je ne sais pas s'il existe une langue qui peut "simplement" faire la majorité du code dans ce projet sûr par définition.
Les tripes de ce projet, les adaptateurs, parlent au noyau. Ils sont tenus d'utiliser des interfaces au niveau du système dangereuses, mal typées et riches en garde.
L'API publique est à peu près à environ 100 lignes, est bien type, bien testée et véhiculée humaine. Il n'y a pas grand-chose là-bas.
Créer un FFI en exposant les adaptateurs avec un C ABI pourrait en valoir la peine. La plupart des langues devraient être capables de s'accrocher à cela.
La sécurité des adaptateurs de la plate-forme dépend nécessairement de la documentation de chaque plate-forme pour leurs interfaces. Comme avec toutes les interfaces au niveau du système, tant que nous assurons les conditions pré-et postes correctes et que ces conditions sont bien définies, tout devrait aller bien.
Parmi les implémentations spécifiques à la plate-forme, l'API FSEvents est utilisée sur Darwin et l'API ReadDirectoryChanges est utilisée sur Windows. Il y a un travail supplémentaire que nous faisons pour sélectionner le meilleur adaptateur sur Linux. L'adaptateur fanotify est utilisé lorsque la version du noyau est supérieure à 5,9, le processus de contenu a des privilèges racine et que les appels système nécessaires sont autrement autorisés. Les appels système associés à fanotify peuvent être interdits à l'intérieur d'un conteneur ou d'un CGROUP, malgré les privilèges nécessaires et la version du noyau. L'adaptateur inotify est utilisé autrement. Vous pouvez trouver le code de sélection pour Linux ici.
Les espaces de noms de nos adaptateurs sont en ligne. Lorsque le detail::...::watch() est invoqué, il se résout à une (et une seule) implémentation de plate-forme-spécialisée de la fonction watch() . Un symbole, de nombreuses plates-formes, où les plates-formes sont des espaces de noms en ligne.
L'efficacité prend un coup lorsque nous faisons ressortir le warthog , notre adaptateur indépendant de la plate-forme. Cet adaptateur est utilisé sur des plateformes qui manquent de meilleures alternatives, comme (pas Darwin) BSD et Solaris (parce que warthog bat kqueue ).
Watcher est encore relativement efficace lorsqu'il n'a pas de meilleure alternative que warthog . En tant que référence, la numérisation de plus de cent mille voies avec warthog pourrait bégayer.
Je garderai les yeux ouverts pour de meilleures API du noyau sur BSD.
Il n'y a aucun moyen fiable de communiquer lorsqu'un observateur est prêt à envoyer des événements au rappel.
Pour quelques milliers de chemins, cela peut prendre quelques millisecondes. Pour des dizaines de milliers de chemins, envisagez d'attendre quelques secondes.
Aucune des implémentations spécifiques à la plate-forme ne fournit des informations sur les attributs qui ont été modifiés. Cela rend nous-mêmes le soutien à ces événements qui dépendent du stockage de ces informations. Le stockage de cartes des chemins vers les structures stat , les diffusant sur les changements d'attribut, est un engagement de mémoire non insignifiant.
Les événements du propriétaire et des attributs ne sont pas pris en charge car je ne sais pas comment soutenir ces événements efficaces.
Les systèmes de fichiers spéciaux, y compris /proc et /sys , ne peuvent pas être surveillés avec inotify , fanotify ou warthog . Les travaux futurs peuvent impliquer la suppression des programmes EBPF pour le noyau. Cela nous permettrait de surveiller pour modify les événements sur certains de ces systèmes de fichiers spéciaux.
Le nombre de fichiers regardés est limité lorsque inotify est utilisé.
Pour la bibliothèque d'en-tête uniquement et le minuscule watcher, C ++ 17 et plus devraient être bien.
Nous pourrions utiliser un jour C ++ 20 Coroutines.
$ tool/gen-event/dir &
$ tool/gen-event/file &
$ valgrind --tool=cachegrind wtr.watcher ~ -s 30I refs: 797,368,564
I1 misses: 6,807
LLi misses: 2,799
I1 miss rate: 0.00%
LLi miss rate: 0.00%
D refs: 338,544,669 (224,680,988 rd + 113,863,681 wr)
D1 misses: 35,331 ( 24,823 rd + 10,508 wr)
LLd misses: 11,884 ( 8,121 rd + 3,763 wr)
D1 miss rate: 0.0% ( 0.0% + 0.0% )
LLd miss rate: 0.0% ( 0.0% + 0.0% )
LL refs: 42,138 ( 31,630 rd + 10,508 wr)
LL misses: 14,683 ( 10,920 rd + 3,763 wr)
LL miss rate: 0.0% ( 0.0% + 0.0% ) Les espaces de noms et les symboles suivent de près les répertoires dans le dossier devel/include . Les espaces de noms en ligne sont dans les répertoires avec l' - .
Par exemple, wtr::watch se trouve à l'intérieur du fichier devel/include/wtr/watcher-/watch.hpp . L' watcher de l'espace de noms dans wtr::watcher::watch est anonyme par cette convention.
Plus en profondeur: la fonction ::detail::wtr::watcher::adapter::watch() est définie à l'intérieur d'un (et une seule!) Des fichiers devel/include/detail/wtr/watcher/adapter/*/watch.hpp , où * est décidé au compilé-temps (en fonction du système d'exploitation de l'hôte).
Tous les en-têtes dans devel/include sont fusionnés en include/wtr/watcher.hpp et un gardien d'includ est ajouté au sommet. L'includ Guard ne change pas avec la version de version. À l'avenir, cela pourrait.
watcher
├── src
│ └── wtr
│ ├── watcher
│ │ └── main.cpp
│ └── tiny_watcher
│ └── main.cpp
├── out
├── include
│ └── wtr
│ └── watcher.hpp
└── devel
├── src
│ └── wtr
└── include
├── wtr
│ ├── watcher.hpp
│ └── watcher-
│ ├── watch.hpp
│ └── event.hpp
└── detail
└── wtr
└── watcher
├── semabin.hpp
└── adapter
├── windows
│ └── watch.hpp
├── warthog
│ └── watch.hpp
├── linux
│ ├── watch.hpp
│ ├── sysres.hpp
│ ├── inotify
│ │ └── watch.hpp
│ └── fanotify
│ └── watch.hpp
└── darwin
└── watch.hpp
Vous pouvez exécuter
tool/treepour afficher cet arbre localement.
https://github.com/notify-rs/notify :
lines of code : 2799
lines of tests : 475
lines of docs : 1071
implementation languages : rust
interface languages : rust
supported platforms : linux, windows, darwin, bsd
kernel apis : inotify, readdirectorychanges, fsevents, kqueue
non-blocking : yes
dependencies : none
tests : yes
static analysis : yes (borrow checked, memory and concurrency safe language)
https://github.com/e-dant/watcher :
lines of code : 1579
lines of tests : 881
lines of docs : 1977
implementation languages : cpp
interface languages : cpp, shells
supported platforms : linux, darwin, windows, bsd
kernel apis : inotify, fanotify, fsevents, readdirectorychanges
non-blocking : yes
dependencies : none
tests : yes
static analysis : yes
https://github.com/facebook/watchman.git :
lines of code : 37435
lines of tests : unknown
lines of docs : unknown
implementation languages : cpp, c
interface languages : cpp, js, java, python, ruby, rust, shells
supported platforms : linux, darwin, windows, maybe bsd
kernel apis : inotify, fsevents, readdirectorychanges
non-blocking : yes
dependencies : none
tests : yes (many)
static analysis : yes (all available)
https://github.com/p-ranav/fswatch :
lines of code : 245
lines of tests : 19
lines of docs : 114
implementation languages : cpp
interface languages : cpp, shells
supported platforms : linux, darwin, windows, bsd
kernel apis : inotify
non-blocking : maybe
dependencies : none
tests : some
static analysis : none
https://github.com/tywkeene/go-fsevents :
lines of code : 413
lines of tests : 401
lines of docs : 384
implementation languages : go
interface languages : go
supported platforms : linux
kernel apis : inotify
non-blocking : yes
dependencies : yes
tests : yes
static analysis : none (gc language)
https://github.com/radovskyb/watcher :
lines of code : 552
lines of tests : 767
lines of docs : 399
implementation languages : go
interface languages : go
supported platforms : linux, darwin, windows
kernel apis : none
non-blocking : no
dependencies : none
tests : yes
static analysis : none
https://github.com/parcel-bundler/watcher :
lines of code : 2862
lines of tests : 474
lines of docs : 791
implementation languages : cpp
interface languages : js
supported platforms : linux, darwin, windows, maybe bsd
kernel apis : fsevents, inotify, readdirectorychanges
non-blocking : yes
dependencies : none
tests : some (js bindings)
static analysis : none (interpreted language)
https://github.com/atom/watcher :
lines of code : 7789
lines of tests : 1864
lines of docs : 1334
implementation languages : cpp
interface languages : js
supported platforms : linux, darwin, windows, maybe bsd
kernel apis : inotify, fsevents, readdirectorychanges
non-blocking : yes
dependencies : none
tests : some (js bindings)
static analysis : none
https://github.com/paulmillr/chokidar :
lines of code : 1544
lines of tests : 1823
lines of docs : 1377
implementation languages : js
interface languages : js
supported platforms : linux, darwin, windows, bsd
kernel apis : fsevents
non-blocking : maybe
dependencies : yes
tests : yes (many)
static analysis : none (interpreted language)
https://github.com/Axosoft/nsfw :
lines of code : 2536
lines of tests : 1085
lines of docs : 148
implementation languages : cpp
interface languages : js
supported platforms : linux, darwin, windows, maybe bsd
kernel apis : fsevents
non-blocking : maybe
dependencies : yes (many)
tests : yes (js bindings)
static analysis : none
https://github.com/canton7/SyncTrayzor :
lines of code : 17102
lines of tests : 0
lines of docs : 2303
implementation languages : c #
interface languages : c #
supported platforms : windows
kernel apis : unknown
non-blocking : yes
dependencies : unknown
tests : none
static analysis : none (managed language)
https://github.com/g0t4/Rx-FileSystemWatcher :
lines of code : 360
lines of tests : 0
lines of docs : 46
implementation languages : c #
interface languages : c #
supported platforms : windows
kernel apis : unknown
non-blocking : yes
dependencies : unknown
tests : yes
static analysis : none (managed language)