# 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 ( ) ;
} ) ;A saída de cada um acima será algo isso, dependendo do 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
Aproveitar!
Um observador de eventos de sistema de arquivos que é
Eu tento manter as linhas 1579 que compõem o tempo de execução do Watcher relativamente simples e a API prática:
auto w = watch(path, [](event ev) { cout << ev; });wtr.watcher ~O observador pode ser usado como uma biblioteca, um programa ou ambos . Se você não está procurando criar algo com a biblioteca, não se preocupe. Basta usar o nosso e você terá um observador do sistema de arquivos que imprime os eventos do sistema de arquivos como JSON. Organizado. Aqui 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.)Você pode assistir a um sistema de arquivos inteiro com este projeto. Em quase todos os casos, usamos uma quantidade quase zero de recursos e fazemos uso eficiente do cache. Testamos regularmente que a sobrecarga de detectar e enviar um evento para o usuário é uma ordem de magnitude menor que as operações do sistema de arquivos que estão sendo medidas.
Executamos este projeto através de testes de unidade contra todos os desinfetantes disponíveis. Esse código se esforça para ser thread, memória, limites, tipo e segura de recursos. O que nos falta do idioma, tentamos compensar os testes. Para alguma definição prática de segurança, este projeto provavelmente se encaixa.
O observador depende da biblioteca padrão C ++. Para eficiência, aproveitamos o sistema operacional quando possível no Linux, Darwin e Windows. Para testes e depuração, usamos snitch e desinfetantes.
O observador é executável em quase qualquer lugar. O único requisito é um sistema de arquivos.
As peças importantes são a biblioteca (somente para cabeçalho) e o programa CLI (opcional).
include/wtr/watcher.hpp . Inclua isso para usar o observador no seu projeto C ++. Copiar isso em seu projeto e incluí -lo como #include "wtr/watcher.hpp" (ou similar) é suficiente para subir e funcionar nesse idioma. Alguma documentação extra e internos da biblioteca de alto nível podem ser encontrados no evento e assistir cabeçalhos.watcher-c . Construa isso para usar o observador de C ou através de um FFI em outros idiomas.src/wtr/watcher/main.cpp . Crie isso para usar o observador da linha de comando. A saída é um fluxo JSON exaustivo.src/wtr/tiny_watcher/main.cpp . Um programa CLI muito mínimo e mais legível por humanos. A fonte para isso é quase idêntica ao exemplo de uso para C ++.Uma árvore de diretório está nas notas abaixo.
Os dois blocos fundamentais de construção aqui estão:
watch ou classe (dependendo do idioma)event (ou nomeado de maneira semelhante, novamente dependendo do idioma) watch segue um caminho, que é uma coisa semelhante a uma corda, e um retorno de chamada, com uma coisa de função. Por exemplo, a watch de uma matriz de assistência e um fechamento funcionaria bem no C ++.
Exemplos para uma variedade de idiomas podem ser encontrados no início rápido. A API é relativamente consistente entre os idiomas.
O vigia continuará assistindo até você pará -lo ou atingir um erro irrecuperável.
O objeto event é usado para passar informações sobre os eventos do sistema de arquivos para o retorno de chamada dado (por você) para watch .
O objeto event conterá:
path_name , que é um caminho absoluto para o evento.path_type , o tipo de caminho. Um de:dirfilehard_linksym_linkwatcherothereffect_type , "O que aconteceu". Um de:renamemodifycreatedestroyownerothereffect_time , o tempo do evento em nanossegundos desde a época.associated (um evento, C ++) ou associated_path_name (todas as outras implementações, um único nome de caminho):(Observe que, para JavaScript, usamos o caso de camelo, para ser consistente com o ecossistema desse idioma.)
O tipo de watcher é especial.
Os eventos com esse tipo incluirão mensagens do vigia. Você pode receber mensagens de erro ou atualizações de status importantes.
Esse formato foi escolhido para suportar mensagens assíncronas do observador em um formato genérico e portátil.
Dois dos eventos mais importantes do "Watcher" são o evento inicial "Live" e o evento final "Die".
A mensagem aparece presa ao caminho da base assistida.
Por exemplo, depois de abrir um observador em /a/path , você pode receber essas mensagens do vigia:
s/self/live@/a/pathe/self/die@/a/path As mensagens sempre começam com um s , indicando uma operação bem-sucedida, A w , indicando um aviso não fatal ou um e , indicando um erro fatal.
É importante ressaltar que o fechamento do observador sempre produzirá um erro se
self/live ainda não foi enviada; Ou, em outras palavras, se o observador não tiver iniciado completamente. Nesse caso, o observador fechará imediatamente após a abertura e relatar totalmente um erro em todas as chamadas para fechar. O último evento sempre será um evento destroy do vigia. Você pode analisar assim, para algum evento ev :
ev.path_type == path_type::watcher && ev.effect_type == effect_type::destroy;Hacking feliz.
Este projeto tenta facilitar o trabalho com os eventos do sistema de arquivos. Eu acho que boas ferramentas são fáceis de usar. Se este projeto não for ergonômico, registre um problema.
Aqui está um instantâneo da saída tirada ao preparar esse compromisso, logo antes de escrever este parágrafo.
{
"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 "
}
}O que é bem legal.
Um programa capaz está aqui.
Este projeto é acessível através de:
tool/build : inclui os alvos C ++ Cabeçalho/Biblioteca, CLI, teste e referênciatool/cross : inclui a biblioteca e o cabeçalho compartilhados watcher-c , compilados cruzados para muitas plataformaswatcher-c (estática e compartilhada), CLI, Teste e metas de referênciaVeja o pacote aqui.
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 10Isso cuidará de alguns específicos da plataforma, criando variantes de lançamento, depuração e desinfetante e executando alguns testes.
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 10Observadores de todas as plataformas ignoram intencionalmente os eventos de modificação que alteram apenas o tempo de acesso em um arquivo ou diretório.
A utilidade desses eventos foi questionável.
Parecia mais prejudicial do que bom. Outros observadores, como o observador C# da Microsoft, ignoram -os por padrão. Alguns aplicativos de usuário dependem de eventos de modificação para saber quando eles mesmos recarregam um arquivo.
Existem soluções melhores e mais completas, e esses padrões podem mudar novamente.
Fornecendo uma maneira de ignorar os eventos de um processo de processo, uma abreviação do processo de "esse" e uma maneira de especificar em quais tipos de fontes de eventos em que estamos interessados são bons candidatos a soluções mais completas.
Eu estava confortável com o C ++ quando escrevi isso pela primeira vez. Mais tarde, reescrevi este projeto em Rust como um experimento. Existem benefícios e desvantagens para a ferrugem. Algumas coisas eram um pouco mais seguras de expressar, outras coisas definitivamente não eram. A necessidade de fazer matemática do ponteiro em alguns tipos opacos de tamanho variável do kernel, por exemplo, não é mais seguro expressar em ferrugem. Outras coisas são mais seguras, mas esse projeto não se beneficia muito com elas.
A ferrugem realmente brilha em usabilidade e expressão. Isso pode ser um motivo suficiente para usá -lo. Entre outras coisas, poderíamos trabalhar com características assíncronas e tipos algébricos para um grande bem.
Não tenho certeza se existe um idioma que pode "apenas" tornar a maior parte do código neste projeto seguro por definição.
As tripas deste projeto, os adaptadores, conversam com o kernel. Eles são obrigados a usar interfaces inseguras, mal-tipadas e ricas em advertências.
A API pública é de cerca de 100 linhas, é bem tocada, bem testada e verificável humano. Não acontece muito lá.
Criar um FFI expondo os adaptadores com um C ABI pode valer a pena. A maioria das línguas deve ser capaz de se conectar a isso.
A segurança dos adaptadores de plataforma depende necessariamente da documentação de cada plataforma para suas interfaces. Como em todas as interfaces no nível do sistema, desde que garantamos as condições pré-e-post corretas, e essas condições estejam bem definidas, devemos ficar bem.
Entre as implementações específicas da plataforma, a API FSEvents é usada no Darwin e a API ReadDirectoryChanges é usada no Windows. Há algum trabalho extra que fazemos para selecionar o melhor adaptador no Linux. O adaptador fanotify é usado quando a versão do kernel é maior que 5,9, o processo contendo possui privilégios root e as chamadas necessárias do sistema são permitidas de outra forma. As chamadas do sistema associadas ao fanotify podem ser não permitidas quando dentro de um contêiner ou CGROUP, apesar das priviléias necessárias e da versão do kernel. O adaptador inotify é usado de outra forma. Você pode encontrar o código de seleção do Linux aqui.
Os namespaces para nossos adaptadores estão embutidos. Quando a função (interna) detail::...::watch() é invocada, ela resolve uma (e apenas um) implementação da plataforma-especificação da função watch() . Um símbolo, muitas plataformas, onde as plataformas são espaços de nome em linha.
A eficiência é atingida quando trazemos o warthog , nosso adaptador independente de plataforma. Este adaptador é usado em plataformas que não têm melhores alternativas, como (não Darwin) BSD e Solaris (porque warthog vence kqueue ).
O observador ainda é relativamente eficiente quando não tem alternativa melhor que warthog . Como uma regra do polegar, examinar mais de cem mil caminhos com warthog pode gaguejar.
Vou manter meus olhos abertos para melhores APIs do kernel no BSD.
Não há uma maneira confiável de se comunicar quando um observador está pronto para enviar eventos para o retorno de chamada.
Por alguns milhares de caminhos, isso pode levar alguns milissegundos. Para dezenas de milhares de caminhos, considere esperar alguns segundos.
Nenhuma das implementações específicas da plataforma fornece informações sobre quais atributos foram alterados. Isso torna o apoio a esses eventos dependentes de armazenar essas informações. O armazenamento de mapas de caminhos para as estruturas stat , diferendo-os nas mudanças de atributo, é um compromisso de memória não significativo.
Os eventos do proprietário e do atributo não são suportados porque não tenho certeza de como apoiar esses eventos eficientes.
Os sistemas de arquivos especiais, incluindo /proc e /sys , não podem ser assistidos com inotify , fanotify ou o warthog . Trabalhos futuros podem envolver a expedição de programas EBPF para o kernel usar. Isso nos permitiria monitorar os eventos de modify alguns desses sistemas de arquivos especiais.
O número de arquivos assistidos é limitado quando inotify é usado.
Para a biblioteca somente para cabeçalho e o minúsculo observador, o C ++ 17 ou mais deve estar bem.
Podemos usar C ++ 20 Coroutines algum dia.
$ 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 e símbolos seguem de perto os diretórios na pasta devel/include . Os namespaces embutidos estão em diretórios com o - .
Por exemplo, wtr::watch está dentro do arquivo devel/include/wtr/watcher-/watch.hpp . O watcher de namespace em wtr::watcher::watch é anônimo por esta convenção.
Mais em profundidade: a função ::detail::wtr::watcher::adapter::watch() é definido dentro de um (e apenas um!) Dos arquivos devel/include/detail/wtr/watcher/adapter/*/watch.hpp , onde * é decidido no tempo de compilação (dependendo do sistema operacional do host).
Todos os cabeçalhos em devel/include são amalgamados em include/wtr/watcher.hpp e um guarda incluído é adicionado à parte superior. O guarda incluído não muda com a versão de liberação. No futuro, pode.
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
Você pode executar
tool/treepara visualizar esta árvore 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)