# 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 ( ) ;
} ) ;Die Ausgabe von jedem oben genannten wird je nach Format etwas sein:
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
Genießen!
Ein Dateisystemereignisbeobachter, der ist
Ich versuche, die 1579 Zeilen zu behalten, aus denen die Laufzeit des Beobachter relativ einfach und die API praktisch besteht:
auto w = watch(path, [](event ev) { cout << ev; });wtr.watcher ~Beobachter kann als Bibliothek, als Programm oder beides verwendet werden. Wenn Sie nicht mit der Bibliothek etwas erstellen möchten, machen Sie sich keine Sorgen. Verwenden Sie einfach unsere und Sie haben einen Dateisystembeobachter, der Dateisystemereignisse als JSON druckt. Sauber. So wie: wie:
# 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.)Sie können ein ganzes Dateisystem mit diesem Projekt ansehen. In fast allen Fällen verwenden wir eine Menge Ressourcen nahe Null und nutzen den Cache effizient. Wir testen regelmäßig, dass der Overhead der Erkennung und Senden eines Ereignisses an den Benutzer eine Größenordnung ist, die weniger als die gemessenen Dateisystemvorgänge sind.
Wir führen dieses Projekt über Unit -Tests gegen alle verfügbaren Sanitärer durch. Dieser Code bemüht sich, Thread, Speicher, Grenzen, Typ und Ressourcensicherheit zu sein. Was uns aus der Sprache fehlt, versuchen wir, mit Tests auszugleichen. Für eine praktische Definition von Sicherheit passt dieses Projekt wahrscheinlich.
Der Beobachter hängt von der C ++ - Standardbibliothek ab. Für die Effizienz nutzen wir das Betriebssystem, wenn möglich, unter Linux, Darwin und Windows. Zum Testen und Debuggen verwenden wir Snitch und Desinfektionsmittel.
Der Beobachter ist fast überall runnierbar. Die einzige Anforderung ist ein Dateisystem.
Die wichtigen Teile sind die (nur Header) Bibliothek und das (optionale) CLI-Programm.
include/wtr/watcher.hpp . Fügen Sie dies zu Watcher in Ihr C ++ - Projekt ein. Wenn Sie dies in Ihr Projekt kopieren und es als #include "wtr/watcher.hpp" (oder ähnlich) einschließen, reicht es aus, um in dieser Sprache in Betrieb zu nehmen. Einige zusätzliche Dokumentation und Aufnahme in Bibliotheksbibliotheken finden Sie in den Event- und Watch-Headern.watcher-c . Bauen Sie dies auf, um den Beobachter aus C oder über einen FFI in anderen Sprachen zu verwenden.src/wtr/watcher/main.cpp . Erstellen Sie dies, um Beobachter aus der Befehlszeile zu verwenden. Der Ausgang ist ein erschöpfender JSON -Stream.src/wtr/tiny_watcher/main.cpp . Ein sehr minimaleres CLI-Programm. Die Quelle dafür ist fast identisch mit der Beispielverwendung für C ++.Ein Verzeichnisbaum befindet sich in den folgenden Notizen.
Die beiden grundlegenden Bausteine hier sind:
watch oder Klasse (abhängig von der Sprache)event (oder ähnlich benannt, je nach Sprache) watch nimmt einen Pfad, der eine stringähnliche Sache ist, und ein Rückruf ist eine funktionsähnliche Sache. Wenn watch beispielsweise ein Charakter -Array und eine Schließung in C ++ gut bestanden haben.
Beispiele für eine Vielzahl von Sprachen finden Sie im schnellen Start. Die API ist über Sprachen hinweg relativ konsistent.
Der Beobachter wird gerne weiter beobachten, bis Sie es stoppen oder es einen nicht wiederbezogenen Fehler trifft.
Das event wird verwendet, um Informationen über Dateisystemereignisse an den zurückgegebenen Rückruf zu übergeben (von Ihnen), um sie zu watch .
Das event enthält:
path_name , der ein absoluter Weg zum Ereignis ist.path_type , die Art des Pfades. Einer von:dirfilehard_linksym_linkwatcherothereffect_type , "Was ist passiert". Einer von:renamemodifycreatedestroyownerothereffect_time , die Zeit des Ereignisses in Nanosekunden seit der Epoche.associated (ein Ereignis, C ++) oder associated_path_name (alle anderen Implementierungen, ein einzelner Pfadname):(Beachten Sie, dass wir für JavaScript den Kamelfall verwenden, um mit dem Ökosystem dieser Sprache übereinzustimmen.)
Der watcher ist etwas Besonderes.
Ereignisse mit diesem Typ enthalten Nachrichten vom Beobachter. Sie können Fehlermeldungen oder wichtige Statusaktualisierungen erhalten.
Dieses Format wurde ausgewählt, um asynchrone Nachrichten vom Beobachter in einem generischen, tragbaren Format zu unterstützen.
Zwei der wichtigsten "Beobachter" -Events sind das anfängliche "Live" -Ervent und das letzte "Die" -Event.
Die Nachricht wird angezeigt, die auf den angesehenen Basispfad vorbereitet wird.
Nachdem Sie beispielsweise einen Beobachter AT /a/path geöffnet haben, können Sie diese Nachrichten vom Beobachter erhalten:
s/self/live@/a/pathe/self/die@/a/path Die Nachrichten beginnen immer entweder mit einem s , was auf eine erfolgreiche Operation hinweist, ein w , was auf eine nicht tödliche Warnung oder ein e hinweist, was auf einen tödlichen Fehler hinweist.
Wichtig ist, dass das Schließen des Beobachter immer einen Fehler erzeugt, wenn
self/live -Nachricht wurde noch nicht gesendet; Oder mit anderen Worten, wenn der Beobachter nicht vollständig begonnen hat. In diesem Fall schließt der Beobachter sofort, nachdem er einen Fehler in allen Anrufen zum Schließen vollständig geöffnet und meldet. Das letzte Ereignis wird immer ein destroy des Beobachters sein. Sie können es so analysieren, für ein Ereignis ev :
ev.path_type == path_type::watcher && ev.effect_type == effect_type::destroy;Frohe Hacking.
Dieses Projekt versucht es Ihnen leicht zu erleichtern, mit Dateisystemereignissen zu arbeiten. Ich denke, gute Werkzeuge sind einfach zu bedienen. Wenn dieses Projekt nicht ergonomisch ist, stellen Sie ein Problem ein.
Hier ist eine Momentaufnahme der Ausgabe, die bei der Vorbereitung dieses Commits kurz vor dem Schreiben dieses Absatzes erstellt wurde.
{
"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 "
}
}Das ist ziemlich cool.
Ein fähiges Programm ist hier.
Dieses Projekt ist zugänglich über:
tool/build : Enthält die C ++ - Header/Bibliothek, CLI-, Test- und Benchmark -Zieletool/cross : Beinhaltet die gemeinsame Bibliothek und den Header watcher-c , die für viele Plattformen mit dem Kompilieren gekreuzigt wurdewatcher-c Bibliothek (statisch und geteilt), CLI-, Test- und Benchmark-ZieleSiehe das Paket hier.
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 10Dies wird sich um einige plattformspezifische, die Veröffentlichung, Debug- und Desinfektionsmittelvarianten erstellen und einige Tests durchführen.
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 10Beobachter auf allen Plattformen ignorieren absichtlich Änderungsereignisse, die nur die Spurzeit in einer Datei oder einem Verzeichnis ändern.
Der Nutzen dieser Ereignisse war fragwürdig.
Es schien schädlicher als gut. Andere Beobachter, wie Microsofts C# Watcher, ignorieren sie standardmäßig. Einige Benutzeranwendungen stützen sich auf Änderungsereignisse, um zu wissen, wann sie eine Datei neu laden.
Bessere, vollständigere Lösungen gibt es, und diese Standardeinstellungen könnten sich wieder ändern.
Bereitstellung einer Möglichkeit, Ereignisse von einem Prozess zu ignorieren, eine Abkürzung aus "diesem" Prozess und eine Möglichkeit, anzugeben, welche Arten von Ereignisquellen, an denen wir interessiert sind, gute Kandidaten für vollständigere Lösungen sind.
Ich war mit C ++ wohl, als ich das zum ersten Mal schrieb. Ich habe dieses Projekt später in Rust als Experiment neu geschrieben. Es gibt Vorteile und Nachteile von Rost. Einige Dinge waren etwas sicherer, um andere Dinge definitiv nicht zu machen. Die Notwendigkeit, Zeigermathematik auf einigen variabel großen undurchsichtigen Typen aus dem Kernel aus dem Kern zu machen, ist beispielsweise nicht sicherer in Rost auszudrücken. Andere Dinge sind sicherer, aber dieses Projekt profitiert nicht sehr von ihnen.
Rost scheint wirklich in Benutzerfreundlichkeit und Ausdruck. Das könnte genug von einem Grund sein, es zu verwenden. Unter anderem könnten wir mit asynchronen Merkmalen und algebraischen Typen für ein gutes Wohl arbeiten.
Ich bin mir nicht sicher, ob es eine Sprache gibt, die "einfach" den größten Teil des Code in diesem Projekt per Definition sicher machen kann.
Der Eingeweide dieses Projekts, die Adapter, sprechen mit dem Kernel. Sie verwenden unsichere, schlecht getotische, einschränkende Schnittstellen auf Systemebene auf Systemebene.
Die öffentliche API ist nur rund 100 Zeilen, ist gut getestet, gut getestet und menschlich überprüfbar. Dort passiert nicht viel.
Das Erstellen eines FFI, indem Sie die Adapter mit einem C ABI freilegen, ist möglicherweise wert. Die meisten Sprachen sollten in der Lage sein, sich daran zu beschäftigen.
Die Sicherheit der Plattformadapter hängt notwendigerweise von der Dokumentation jeder Plattform für ihre Schnittstellen ab. Wie bei allen Schnittstellen auf Systemebene, solange wir die korrekten Vor- und Post-Konditionen sicherstellen und diese Bedingungen gut definiert sind, sollten wir in Ordnung sein.
Unter den plattformspezifischen Implementierungen wird die FSEvents -API für Darwin verwendet und die API ReadDirectoryChanges wird unter Windows verwendet. Wir machen einige zusätzliche Arbeiten, um den besten Adapter unter Linux auszuwählen. Der fanotify -Adapter wird verwendet, wenn die Kernelversion größer als 5,9 ist, der enthaltene Prozess in Root -Berechtigungen und die erforderlichen Systemaufrufe sind ansonsten zulässig. Die mit fanotify verbundenen Systemaufrufe können trotz der erforderlichen Priviledges und Kernel -Versionen in einem Container oder einer CGroup nicht zugelassen werden. Der inotify -Adapter wird ansonsten verwendet. Hier finden Sie den Auswahlcode für Linux.
Die Namespaces für unsere Adapter sind inline. Wenn die (interne) detail::...::watch() aufgerufen wird, wird eine (und nur eine) Plattform-Specifc-Implementierung der Funktion der watch() aufgelöst. Ein Symbol, viele Plattformen, auf denen die Plattformen Inline -Namespaces sind.
Effizienz ist ein Treffer, wenn wir den warthog , unseren plattformunabhängigen Adapter, herausbringen. Dieser Adapter wird auf Plattformen verwendet, denen bessere Alternativen wie (nicht Darwin) BSD und Solaris (weil warthog kqueue schlägt) fehlen.
Der Beobachter ist immer noch relativ effizient, wenn er keine Alternative besser als warthog hat. Als Daumenregel könnte das Scannen von mehr als einhunderttausend Wegen mit warthog stottern.
Ich werde meine Augen für bessere Kernel -APIs auf BSD offen halten.
Es gibt keine zuverlässige Möglichkeit, zu kommunizieren, wenn ein Beobachter bereit ist, Ereignisse an den Rückruf zu senden.
Für ein paar tausend Wege kann dies ein paar Millisekunden dauern. Für zehntausende Wege sollten Sie ein paar Sekunden lang warten.
Keine der plattformspezifischen Implementierungen enthält Informationen darüber, aus welchen Attributen geändert wurde. Dies macht die Unterstützung dieser Ereignisse abhängig von der Speicherung dieser Informationen selbst. Das Speichern von Karten von Pfaden in stat Strukturen, die sie in Attributänderungen verbreiten, ist eine nicht-signifikante Gedächtnisverpflichtung.
Die Eigentümer- und Attributereignisse werden nicht unterstützt, da ich nicht sicher bin, wie ich diese Ereignisse effizient unterstützt.
Spezielle Dateisysteme, einschließlich /proc und /sys , können nicht mit inotify , fanotify oder dem warthog beobachtet werden. Zukünftige Arbeiten können das Versenden von EBPF -Programmen für den Kernel beinhalten. Dies würde es uns ermöglichen, Ereignisse in einigen dieser speziellen Dateisysteme zu modify .
Die Anzahl der angesehenen Dateien ist begrenzt, wenn inotify verwendet wird.
Für die Nur-Header-Bibliothek und die winzige Beobachter sollten C ++ 17 und Up in Ordnung sein.
Wir könnten eines Tages C ++ 20 Coroutinen verwenden.
$ 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% ) Namespaces und Symbole folgen genau den Verzeichnissen im devel/include Ordner. Inline -Namespaces befinden sich in Verzeichnissen mit dem - Affix.
Zum Beispiel befindet sich wtr::watch in der Datei devel/include/wtr/watcher-/watch.hpp . Der Namespace watcher in wtr::watcher::watch ist durch diese Konvention anonym.
In der Tiefe: Die Funktion ::detail::wtr::watcher::adapter::watch() ist in einem (und nur einem!) Der Dateien devel/include/detail/wtr/watcher/adapter/*/watch.hpp definiert, wo * zum Kompilierungszeit (abhängig vom Betriebssystem des Hosts) entschieden wird.
Alle Header in devel/include werden in include/wtr/watcher.hpp zusammengefasst, und ein inklusive Wache wird nach oben hinzugefügt. Die Include -Wache ändert sich nicht mit der Release -Version. In Zukunft könnte es.
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
Sie können
tool/treeausführen, um diesen Baum lokal anzuzeigen.
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)