# 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 salida de cada uno anterior será algo esto, dependiendo del formato:
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
¡Disfrutar!
Un observador de eventos del sistema de archivos que es
Intento mantener las líneas 1579 que componen el tiempo de ejecución del observador relativamente simple y la API práctica:
auto w = watch(path, [](event ev) { cout << ev; });wtr.watcher ~El observador puede usarse como biblioteca, un programa o ambos . Si no está buscando crear algo con la biblioteca, no se preocupe. Simplemente use el nuestro y obtendrá un observador del sistema de archivos que imprime los eventos del sistema de archivos como JSON. Limpio. Aquí está como:
# 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.)Puede ver un sistema de archivos completo con este proyecto. En casi todos los casos, utilizamos una cantidad de recursos casi cero y hacemos un uso eficiente del caché. Regularmente probamos que la sobrecarga de detectar y enviar un evento al usuario es un orden de magnitud menor que las operaciones del sistema de archivos que se miden.
Ejecutamos este proyecto a través de pruebas unitarias contra todos los desinfectados disponibles. Este código intenta ser el subproceso, la memoria, los límites, el tipo y los recursos seguros. Lo que nos falta del idioma, tratamos de compensar con las pruebas. Para una definición práctica de seguridad, este proyecto probablemente encaja.
El observador depende de la biblioteca estándar C ++. Para la eficiencia, aprovechamos el sistema operativo cuando sea posible en Linux, Darwin y Windows. Para las pruebas y la depuración, usamos Snitch y desinfectantes.
Watcher es ejecutable en casi cualquier lugar. El único requisito es un sistema de archivos.
Las piezas importantes son la biblioteca (solo de encabezado) y el programa CLI (opcional).
include/wtr/watcher.hpp . Incluya esto para usar el observador en su proyecto C ++. Copiar esto en su proyecto, e incluirlo como #include "wtr/watcher.hpp" (o similar) es suficiente para ponerse en funcionamiento en este idioma. Se pueden encontrar una documentación adicional y las partes internas de la biblioteca de alto nivel en el evento y los encabezados de observación.watcher-c . Construya esto para usar el observador desde C o a través de un FFI en otros idiomas.src/wtr/watcher/main.cpp . Cree esto para usar el observador desde la línea de comandos. La salida es una secuencia JSON exhaustiva.src/wtr/tiny_watcher/main.cpp . Un programa CLI muy mínimo, legible más humano. La fuente de esto es casi idéntica al uso de ejemplo para C ++.Un árbol de directorio está en las notas a continuación.
Los dos bloques de construcción fundamentales aquí son:
watch o clase (dependiendo del idioma)event (o nombrado de manera similar, nuevamente dependiendo del idioma) watch toma un camino, que es algo similar a una cadena, y una devolución de llamada, es algo similar a la función. Por ejemplo, el paso watch una matriz de personajes y un cierre funcionarían bien en C ++.
Se pueden encontrar ejemplos para una variedad de idiomas en el inicio rápido. La API es relativamente consistente en todos los idiomas.
El observador continuará mirando felizmente hasta que lo detenga o golpea un error irrecuperable.
El objeto event se utiliza para pasar información sobre los eventos del sistema de archivos a la devolución de llamada dada (por usted) para watch .
El objeto event contendrá:
path_name , que es un camino absoluto al evento.path_type , el tipo de ruta. Uno de:dirfilehard_linksym_linkwatcherothereffect_type , "Lo que pasó". Uno de:renamemodifycreatedestroyownerothereffect_time , el tiempo del evento en nanosegundos desde la época.associated (un evento, C ++) o associated_path_name (todas las demás implementaciones, un solo nombre de ruta):(Tenga en cuenta que, para JavaScript, usamos el caso de camello, para ser consistente con el ecosistema de ese lenguaje).
El tipo de watcher es especial.
Los eventos con este tipo incluirán mensajes del observador. Puede recibir mensajes de error o actualizaciones de estado importantes.
Este formato fue elegido para admitir mensajes asincrónicos del observador en un formato genérico y portátil.
Dos de los eventos "Watcher" más importantes son el evento "en vivo" inicial y el evento final "Die".
El mensaje parece prepuesto a la ruta base observada.
Por ejemplo, después de abrir un observador en /a/path , puede recibir estos mensajes del observador:
s/self/live@/a/pathe/self/die@/a/path Los mensajes siempre comienzan con una s , que indican una operación exitosa, una w , que indica una advertencia no fatal o una e , que indica un error fatal.
Es importante destacar que el cierre del observador siempre producirá un error si
self/live aún no se ha enviado; O, en otras palabras, si el observador no ha comenzado completamente. En este caso, el observador se cerrará inmediatamente después de abrir completamente e informará un error en todas las llamadas para cerrar. El último evento siempre será un evento destroy del observador. Puedes analizarlo así, para algún evento ev :
ev.path_type == path_type::watcher && ev.effect_type == effect_type::destroy;Feliz piratería.
Este proyecto trata de facilitarle trabajar con eventos del sistema de archivos. Creo que las buenas herramientas son fáciles de usar. Si este proyecto no es ergonómico, presente un problema.
Aquí hay una instantánea de la salida tomada al preparar este compromiso, justo antes de escribir este párrafo.
{
"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 "
}
}Lo cual es genial.
Un programa capaz está aquí.
Este proyecto es accesible a través de:
tool/build : incluye el encabezado/biblioteca C ++, CLI, prueba y objetivos de referenciatool/cross : incluye la biblioteca y encabezado compartido watcher-c , compilado para muchas plataformaswatcher-c (estática y compartida), CLI, Test and Benchmark TargetsVea el paquete aquí.
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 10Esto se encargará de algunos específicos de la plataforma, la construcción del lanzamiento, la depuración y las variantes desinfectantes, y la ejecución de algunas pruebas.
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 10Los observadores en todas las plataformas ignoran intencionalmente los eventos de modificación que solo cambian el tiempo de acceso en un archivo o directorio.
La utilidad de esos eventos fue cuestionable.
Parecía más dañino que bueno. Otros observadores, como el observador C# de Microsoft, los ignoran por defecto. Algunas aplicaciones de usuarios dependen de eventos de modificación para saber cuándo ellos mismos recargaron un archivo.
Existen mejores soluciones más completas, y estos valores predeterminados podrían cambiar nuevamente.
Proporcionar una forma de ignorar los eventos de un ID de proceso, una abreviatura de "este" proceso y una forma de especificar en qué tipo de fuentes de eventos nos interesan son buenos candidatos para soluciones más completas.
Estaba cómodo con C ++ cuando escribí esto por primera vez. Más tarde reescribí este proyecto en Rust como experimento. Hay beneficios y inconvenientes para el óxido. Algunas cosas eran un poco más seguras de expresar, otras cosas definitivamente no. La necesidad de hacer matemáticas de puntero en algunos tipos opacos de tamaño variable del núcleo, por ejemplo, no es más seguro expresar en óxido. Otras cosas son más seguras, pero este proyecto no se beneficia mucho de ellas.
El óxido realmente brilla en usabilidad y expresión. Esa podría ser una razón suficiente para usarlo. Entre otras cosas, podríamos trabajar con rasgos de asíncrono y tipos algebraicos para un gran bien.
No estoy seguro de si hay un lenguaje que puede "simplemente" hacer que la mayor parte del código en este proyecto sea segura por definición.
Las agallas de este proyecto, los adaptadores, hablan con el núcleo. Están obligados a usar interfaces de nivel de sistema inseguros, mal tipos y ricos en advertencias.
La API pública es de alrededor de 100 líneas, está bien tipo, bien probada y verificable humano. No pasa mucho ahí.
Crear un FFI exponiendo los adaptadores con un C ABI podría valer la pena. La mayoría de los idiomas deberían poder conectarse a eso.
La seguridad de los adaptadores de la plataforma depende necesariamente de la documentación de cada plataforma para sus interfaces. Al igual que con todas las interfaces a nivel de sistema, siempre que nos aseguremos de las condiciones correctas previas y posturas, y esas condiciones están bien definidas, deberíamos estar bien.
Entre las implementaciones específicas de la plataforma, la API FSEvents se utiliza en Darwin y la API ReadDirectoryChanges se usa en Windows. Hay un trabajo adicional que hacemos para seleccionar el mejor adaptador en Linux. El adaptador fanotify se usa cuando la versión del núcleo es mayor que 5.9, el proceso de contenido tiene privelegas raíz y las llamadas del sistema necesarias se permiten de otra manera. Las llamadas del sistema asociadas con fanotify pueden rechazarse cuando están dentro de un contenedor o CGROUP, a pesar de los privilegios necesarios y la versión del núcleo. El adaptador inotify se usa de lo contrario. Puede encontrar el código de selección para Linux aquí.
Los espacios de nombres para nuestros adaptadores están en línea. Cuando se invoca la función (interna) detail::...::watch() , se resuelve a una (y solo una) implementación de la función de la plataforma de la función watch() . Un símbolo, muchas plataformas, donde las plataformas son espacios de nombres en línea.
La eficiencia recibe un éxito cuando sacamos el warthog , nuestro adaptador independiente de la plataforma. Este adaptador se utiliza en plataformas que carecen de mejores alternativas, como (no Darwin) BSD y Solaris (porque warthog vence kqueue ).
El observador sigue siendo relativamente eficiente cuando no tiene una alternativa mejor que warthog . Como regla del pulgar, escanear más de cien mil caminos con warthog podría tartamudear.
Mantendré mis ojos abiertos para obtener mejores API del núcleo en BSD.
No hay una forma confiable de comunicarse cuando un observador está listo para enviar eventos a la devolución de llamada.
Por unos pocos miles de caminos, esto puede tomar unos pocos milisegundos. Para decenas de miles de caminos, considere esperar unos segundos.
Ninguna de las implementaciones específicas de la plataforma proporciona información sobre de qué atributos se cambiaron. Esto hace que el apoyo a esos eventos dependa de almacenar esta información nosotros mismos. El almacenamiento de mapas de rutas a las estructuras stat , que las difunden en los cambios de atributos, es un compromiso de memoria no insignificante.
El propietario y los eventos de atributos no están respaldados porque no estoy seguro de cómo apoyar esos eventos eficientes.
Los sistemas de archivos especiales, que incluyen /proc y /sys , no se pueden observar con inotify , fanotify o The warthog . El trabajo futuro puede implicar el envío de programas EBPF para que el kernel lo use. Esto nos permitiría monitorear para modify eventos en algunos de esos sistemas de archivos especiales.
El número de archivos observados es limitado cuando se usa inotify .
Para la biblioteca de solo encabezado y el pequeño observador, C ++ 17 y arriba debería estar bien.
Podríamos usar C ++ 20 coroutinas algún día.
$ 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% ) Los espacios de nombres y los símbolos siguen de cerca los directorios en la carpeta devel/include . Los espacios de nombres en línea están en directorios con el - Afix.
Por ejemplo, wtr::watch está dentro del archivo devel/include/wtr/watcher-/watch.hpp . El watcher del espacio de nombres en wtr::watcher::watch es anónimo por esta convención.
Más en profundidad: la función ::detail::wtr::watcher::adapter::watch() se define dentro de uno (¡y solo uno!) De los archivos devel/include/detail/wtr/watcher/adapter/*/watch.hpp , donde * se decide en el tiempo de compilación (dependiendo del sistema operativo del host).
Todos los encabezados en devel/include están amalgamados en include/wtr/watcher.hpp y se agrega un protector de incluido a la parte superior. El Guard de incluido no cambia con la versión de lanzamiento. En el futuro, podría.
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
Puede ejecutar
tool/treepara ver este árbol localmente.
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)