Esecuzione di un cron ogni 30 secondi


313

Ok, quindi ho un cron che devo eseguire ogni 30 secondi.

Ecco cosa ho:

*/30 * * * * /bin/bash -l -c 'cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\'''

Funziona, ma funziona ogni 30 minuti o 30 secondi?

Inoltre, ho letto che cron potrebbe non essere lo strumento migliore da usare se lo eseguo così spesso. Esiste un altro strumento migliore che posso usare o installare su Ubuntu 11.04 che sarà un'opzione migliore? C'è un modo per risolvere il cron sopra?


CommaToast e cosa succede se l'app Javascript o Java cade per qualche motivo ed esce? Come si riavvierà? :-)
paxdiablo,

12
Aggiungi una piccola app NodeJS, lol. Perché non una piccola app c ++? Mentre ci siamo, possiamo chiamarlo "cron" ed eseguirlo come servizio.
Andrew,


Ho appena trovato questo mentre guardavo i profili utente e ho visto che eri online meno di 1 ora fa (solo per verificare se l'acc è ancora in uso), c'è qualche motivo specifico per cui non hai accettato nessuna delle risposte di seguito?
Fabian N.

Risposte:


731

Hai */30nello specificatore dei minuti - ciò significa ogni minuto ma con un passo di 30 (in altre parole, ogni mezz'ora). Dal momento cronche non passa alle risoluzioni dei sub-minuti, dovrai trovare un altro modo.

Una possibilità, sebbene sia un po 'kludge (a) , è di avere due lavori, uno sfalsato di 30 secondi:

# Need these to run on 30-sec boundaries, keep commands in sync.
* * * * *              /path/to/executable param1 param2
* * * * * ( sleep 30 ; /path/to/executable param1 param2 )

Vedrai che ho aggiunto commenti e formattato per assicurarmi che sia facile mantenerli sincronizzati.

Entrambi i cronposti di lavoro effettivamente eseguito ogni minuto, ma l' ultima uno aspetterà mezzo minuto prima di eseguire la "carne" del lavoro, /path/to/executable.

Per altre cronopzioni (non basate su), vedere le altre risposte qui, in particolare quelle che menzionano fcrone systemd. Questi sono probabilmente preferibili supponendo che il tuo sistema abbia la possibilità di usarli (come installare fcrono avere una distribuzione al suo systemdinterno).


Se non si desidera utilizzare la soluzione kludgy, è possibile utilizzare una soluzione basata su loop con una piccola modifica. Dovrai comunque gestire mantenendo il processo in esecuzione in qualche forma ma, una volta ordinato, il seguente script dovrebbe funzionare:

#!/bin/env bash

# Debug code to start on minute boundary and to
# gradually increase maximum payload duration to
# see what happens when the payload exceeds 30 seconds.

((maxtime = 20))
while [[ "$(date +%S)" != "00" ]]; do true; done

while true; do
    # Start a background timer BEFORE the payload runs.

    sleep 30 &

    # Execute the payload, some random duration up to the limit.
    # Extra blank line if excess payload.

    ((delay = RANDOM % maxtime + 1))
    ((maxtime += 1))
    echo "$(date) Sleeping for ${delay} seconds (max ${maxtime})."
    [[ ${delay} -gt 30 ]] && echo
    sleep ${delay}

    # Wait for timer to finish before next cycle.

    wait
done

Il trucco è usare un sleep 30ma per avviarlo in background prima dell'esecuzione del payload. Quindi, una volta terminato il payload, attendi che lo sfondo sleepsia terminato.

Se il payload richiede nsecondi (dove n <= 30), l'attesa dopo il payload sarà quindi di 30 - nsecondi. Se occorrono più di 30 secondi, il ciclo successivo verrà ritardato fino al termine del carico utile, ma non più.

Vedrai che ho un codice di debug per iniziare con un limite di un minuto per rendere l'output inizialmente più facile da seguire. Aumento anche gradualmente il tempo di carico utile massimo, quindi alla fine vedrai che il carico utile supera il tempo di ciclo di 30 secondi (viene emessa una riga vuota aggiuntiva, quindi l'effetto è ovvio).

Segue una corsa di esempio (in cui i cicli iniziano normalmente 30 secondi dopo il ciclo precedente):

Tue May 26 20:56:00 AWST 2020 Sleeping for 9 seconds (max 21).
Tue May 26 20:56:30 AWST 2020 Sleeping for 19 seconds (max 22).
Tue May 26 20:57:00 AWST 2020 Sleeping for 9 seconds (max 23).
Tue May 26 20:57:30 AWST 2020 Sleeping for 7 seconds (max 24).
Tue May 26 20:58:00 AWST 2020 Sleeping for 2 seconds (max 25).
Tue May 26 20:58:30 AWST 2020 Sleeping for 8 seconds (max 26).
Tue May 26 20:59:00 AWST 2020 Sleeping for 20 seconds (max 27).
Tue May 26 20:59:30 AWST 2020 Sleeping for 25 seconds (max 28).
Tue May 26 21:00:00 AWST 2020 Sleeping for 5 seconds (max 29).
Tue May 26 21:00:30 AWST 2020 Sleeping for 6 seconds (max 30).
Tue May 26 21:01:00 AWST 2020 Sleeping for 27 seconds (max 31).
Tue May 26 21:01:30 AWST 2020 Sleeping for 25 seconds (max 32).
Tue May 26 21:02:00 AWST 2020 Sleeping for 15 seconds (max 33).
Tue May 26 21:02:30 AWST 2020 Sleeping for 10 seconds (max 34).
Tue May 26 21:03:00 AWST 2020 Sleeping for 5 seconds (max 35).
Tue May 26 21:03:30 AWST 2020 Sleeping for 35 seconds (max 36).

Tue May 26 21:04:05 AWST 2020 Sleeping for 2 seconds (max 37).
Tue May 26 21:04:35 AWST 2020 Sleeping for 20 seconds (max 38).
Tue May 26 21:05:05 AWST 2020 Sleeping for 22 seconds (max 39).
Tue May 26 21:05:35 AWST 2020 Sleeping for 18 seconds (max 40).
Tue May 26 21:06:05 AWST 2020 Sleeping for 33 seconds (max 41).

Tue May 26 21:06:38 AWST 2020 Sleeping for 31 seconds (max 42).

Tue May 26 21:07:09 AWST 2020 Sleeping for 6 seconds (max 43).

Se vuoi evitare la soluzione kludgy, probabilmente è meglio. Avrai comunque bisogno di un cronlavoro (o equivalente) per rilevare periodicamente se questo script è in esecuzione e, in caso contrario, avviarlo. Ma lo stesso script gestisce quindi i tempi.


(a) Alcuni dei miei compagni di lavoro direbbero che i kludges sono la mia specialità :-)


29
Questa è una grande soluzione, tant'è che penso che trascenda la sua sfacciataggine
Domanda Mark

14
@ rubo77, solo se l'esecuzione impiegava meno di un secondo :-) Se impiegassero 29 secondi, accadrebbe alle 0:00:00, 0: 00.59, 0:01:00, 0:01:59 e così via .
paxdiablo,

1
Super subdolo, molto creativo!
K Raphael,

2
Quali sono le parentesi tonde attorno alla seconda riga?
Nigel Alderton,

2
Questa è una soluzione adorabile a un problema che altrimenti paralizza l'efficacia dei croni per determinati compiti che richiedono l'esecuzione in frazioni di minuti. Grazie.
Fiddy Bux,

67

Non puoi. Cron ha una granularità di 60 secondi.

* * * * * cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''
* * * * * sleep 30 && cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''

Questa sintassi è equivalente a quella di paxdiablo? O ci sono differenze sottili?
Nicolas Raoul,

La differenza è: ho usato il percorso originale del binario. @paxdiablo ha usato una sorta di meta-sintassi. (e invoca un sub-shell)
wildplasser

1
Intendo, usando &&invece di ( ; ).
Nicolas Raoul,

2
Scusate. No, c'è una differenza; l' &&operatore cortocircuita, quindi il comando successivo nella catena non viene eseguito se il precedente non è riuscito.
Wildplasser,

Questa granularità non dovrebbe costituire un problema per una risoluzione dei minuti.
Juan Isaza,

37

La granularità di Cron è in pochi minuti e non è stata progettata per svegliarsi ogni xsecondo per eseguire qualcosa. Esegui l'attività ripetuta in un ciclo e dovrebbe fare ciò di cui hai bisogno:

#!/bin/env bash
while [ true ]; do
 sleep 30
 # do what you need to here
done

54
Tieni presente che non è proprio lo stesso. Se il lavoro richiede 25 secondi (ad esempio), inizierà ogni 55 secondi anziché ogni 30 secondi. Potrebbe non essere importante, ma dovresti essere consapevole delle possibili conseguenze.
paxdiablo

7
È possibile eseguire il processo in background, quindi verrà eseguito in quasi 30 secondi esatti.
Chris Koston,

1
mentre [vero] dormi 30 # fai quello che devi fare qui --------- fatto dovrebbe essere in un piccolo caso
tempio

1
Non while [ true ]dovresti avere molte istanze dello stesso script, poiché cron ne avvierà una nuova ogni minuto?
Carcamano,

2
Puoi fare sleep $remainingTimein modo che il tempo rimanente sia 30 meno il tempo impiegato per il lavoro (e limitalo a zero se impiega> 30 secondi). Quindi prendi il tempo prima e dopo il lavoro effettivo e calcoli la differenza.
mahemoff,

32

Se stai eseguendo un recente sistema operativo Linux con SystemD, puoi utilizzare l'unità Timer SystemD per eseguire lo script a qualsiasi livello di granularità desiderato (teoricamente fino a nanosecondi) e, se lo desideri, regole di avvio molto più flessibili di quelle consentite da Cron . Nessun sleepkludges richiesto

Ci vuole un po 'di più per impostare una singola riga in un file cron, ma se hai bisogno di qualcosa di meglio di "Ogni minuto", vale la pena.

Il modello di timer SystemD è fondamentalmente questo: i timer sono unità che avviano unità di servizio quando scade un timer .

Pertanto, per ogni script / comando che si desidera pianificare, è necessario disporre di un'unità di servizio e quindi di un'unità timer aggiuntiva. Una singola unità timer può includere più pianificazioni, quindi normalmente non è necessario più di un timer e un servizio.

Ecco un semplice esempio che registra "Hello World" ogni 10 secondi:

/etc/systemd/system/helloworld.service:

[Unit]
Description=Say Hello
[Service]
ExecStart=/usr/bin/logger -i Hello World

/etc/systemd/system/helloworld.timer:

[Unit]
Description=Say Hello every 10 seconds
[Timer]
OnBootSec=10
OnUnitActiveSec=10
AccuracySec=1ms
[Install]
WantedBy=timers.target

Dopo aver impostato queste unità (in /etc/systemd/system, come descritto sopra, per un'impostazione a livello di sistema o ~/.config/systemd/userper un'impostazione specifica dell'utente), è necessario abilitare il timer (ma non il servizio) eseguendo systemctl enable --now helloworld.timer(il --nowflag avvia anche il timer immediatamente, altrimenti, inizierà solo dopo il prossimo avvio o il login dell'utente).

I [Timer]campi di sezione utilizzati qui sono i seguenti:

  • OnBootSec - avvia il servizio così tanti secondi dopo ogni avvio.
  • OnUnitActiveSec- avviare il servizio così tanti secondi dopo l'ultima volta che è stato avviato il servizio. Questo è ciò che fa sì che il timer si ripeta e si comporti come un lavoro cron.
  • AccuracySec- imposta la precisione del timer. I timer sono accurati solo quanto questo campo imposta e il valore predefinito è 1 minuto (emula cron). Il motivo principale per non richiedere la massima precisione è quello di migliorare il consumo energetico: se SystemD può programmare la prossima corsa in modo che coincida con altri eventi, deve riattivare la CPU meno spesso. L' 1msesempio sopra non è l'ideale - di solito imposto la precisione su 1(1 secondo) nei miei lavori programmati in un minuto, ma ciò significherebbe che se guardi il registro che mostra i messaggi "Hello World", vedresti che è spesso in ritardo di 1 secondo. Se sei d'accordo, ti suggerisco di impostare l'accuratezza su 1 secondo o più.

Come avrai notato, questo timer non imita così bene Cron, nel senso che il comando non si avvia all'inizio di ogni periodo di orologio da parete (ovvero non si avvia al decimo secondo dell'orologio, poi il 20 e così via). Invece succede solo quando il timer si sposta. Se il sistema si è avviato alle 12:05:37, la prossima volta che il comando verrà eseguito sarà alle 12:05:47, quindi alle 12:05:57, ecc. Se sei interessato alla precisione effettiva dell'orologio a muro, allora potresti desidera sostituire le OnBootSece OnUnitActiveSeccampi e invece impostare una OnCalendarregola con il programma che si desidera (che, per quanto ho capito, non può essere più veloce di 1 secondo, utilizzando il formato del calendario). L'esempio sopra può anche essere scritto come:

OnCalendar=*-*-* *:*:00,10,20,30,40,50

Ultima nota: come probabilmente hai indovinato, l' helloworld.timerunità avvia l' helloworld.serviceunità perché hanno lo stesso nome (meno il suffisso del tipo di unità). Questo è il valore predefinito, ma puoi sovrascriverlo impostando il Unitcampo per la [Timer]sezione.

Maggiori dettagli gory sono disponibili all'indirizzo:


2
Mi capita spesso di trovare risposte migliori come questa nella sezione commenti. IMHO, sebbene cron sia stato il punto fermo per i lavori programmati, questa risposta dovrebbe essere accettata poiché non è un "lavoro di hacking" di sleeps e rischia la parallelizzazione dell'esecuzione di attività di lunga durata considerando l'intervallo / frequenza necessari
Qiqo

2
risposta superba, dovrebbe essere la risposta selezionata
GuidedHacking del

21

Non sono necessarie due voci cron, puoi metterle in una con:

* * * * * /bin/bash -l -c "/path/to/executable; sleep 30 ; /path/to/executable"

quindi nel tuo caso:

* * * * * /bin/bash -l -c "cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\'' ; sleep 30 ; cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''"


11
Nota: questo funziona correttamente solo se l'esecuzione dello script richiede meno di un secondo
rubo77

2
Rubo: se il completamento del processo richiede secondi (anziché milli o microsecondi), non lo eseguiresti ogni trenta secondi per poterlo eseguire due volte al minuto. Quindi sì, inizia con 30 quindi sottrai da quello il numero approssimativo di secondi per corsa, se maggiore di 1 secondo.
Andrew,

2
Questo non aiuta anche se si desidera ricevere rapporti sugli errori per ogni esecuzione separatamente.
Joeln,

joelin - il comando che do non impedisce di ottenere dati di log o di output, ho semplificato il comando per rispondere alla domanda. Per acquisire la registrazione, ogni comando può / dovrebbe avere l'output reindirizzato se è necessaria la registrazione, ad esempio script / rails runner -e production '\' 'Song.insert_latest' \ '' potrebbe essere scritto come script / rails runner -e production '\' 'Song.insert_latest' \ '' 2> & 1> / path_to_logfile e di nuovo può essere eseguito per ciascun comando all'interno della singola voce cron.
Andrew,

13

Puoi dare un'occhiata alla mia risposta a questa domanda simile

Fondamentalmente, ho incluso lì uno script bash chiamato "runEvery.sh" che puoi eseguire con cron ogni 1 minuto e passare come argomenti il ​​vero comando che desideri eseguire e la frequenza in secondi in cui vuoi eseguirlo.

qualcosa come questo

* * * * * ~/bin/runEvery.sh 5 myScript.sh


10

Usa l'orologio:

$ watch --interval .30 script_to_run_every_30_sec.sh

posso usare qualcosa del genere $ watch --interval .10 php some_file.php? o watchfunziona solo con file .sh?
Yevhenii Shashkov,

Puoi eseguire qualsiasi cosa con watch. Tuttavia, l'intervallo è compreso tra la fine e l'inizio del comando successivo, quindi --interval .30non verrà eseguito due volte al minuto. Cioè watch -n 2 "sleep 1 && date +%s"aumenterà ogni 3 secondi.
jmartori,

tieni presente che è watchstato progettato per un utilizzo da terminale, quindi - sebbene possa funzionare senza un terminale (esegui con il nohuplogout quindi) o con un terminale falso (come screen) - non ha costi per comportamenti simili a cron, come il recupero da un errore, riavvio dopo l'avvio, ecc. '.
Guss,

9

Il lavoro cron non può essere utilizzato per pianificare un lavoro in intervalli di secondi. cioè non è possibile pianificare l'esecuzione di un processo cron ogni 5 secondi. L'alternativa è scrivere uno script di shell che usa il sleep 5comando in esso.

Crea uno script shell ogni-5-seconds.sh usando bash while loop come mostrato di seguito.

$ cat every-5-seconds.sh
#!/bin/bash
while true
do
 /home/ramesh/backup.sh
 sleep 5
done

Ora, esegui questo script di shell in background usando nohupcome mostrato di seguito. Ciò continuerà a eseguire lo script anche dopo il logout dalla sessione. Questo eseguirà lo script della shell backup.sh ogni 5 secondi.

$ nohup ./every-5-seconds.sh &

4
Il tempo andrà alla deriva. Ad esempio, se l' backup.shesecuzione richiede 1,5 secondi, verrà eseguita ogni 6,5 secondi. Ci sono modi per evitarlo, per esempiosleep $((5 - $(date +%s) % 5))
Keith Thompson,

Sono nuovo di NoHup, mentre eseguo il tuo esempio, Nohup non restituisce "nessun file o directory". Dopo alcune ricerche, sembra che ti sia perso 'sh' dopo il nohup. In questo modo: $ nohup sh ./every-5-seconds.sh &
VHanded

6

Usa fcron ( http://fcron.free.fr/ ) - ti dà granularità in pochi secondi e molto meglio e più ricco di funzionalità rispetto a cron (vixie-cron) e stabile. Ero abituato a fare cose stupide come avere circa 60 script php in esecuzione su una macchina in impostazioni molto stupide e faceva ancora il suo lavoro!


1
Confessioni di uno sviluppatore PHP; )
Eric Kigathi,

3
In realtà, le confessioni di un ingegnere di sistema che consente agli sviluppatori di PHP .... :)
Adi Chiru,

6

in dir /etc/cron.d/

nuovo crea un file excute_per_30s

* * * * * yourusername  /bin/date >> /home/yourusername/temp/date.txt
* * * * * yourusername sleep 30; /bin/date >> /home/yourusername/temp/date.txt

eseguirà cron ogni 30 secondi


4

Attualmente sto usando il metodo seguente. Funziona senza problemi.

* * * * * /bin/bash -c ' for i in {1..X}; do YOUR_COMMANDS ; sleep Y ; done '

Se si desidera eseguire ogni N secondi, poi X sarà 60 / N e S sarà N .

Grazie.


Probabilmente si desidera passare YOUR_COMMANDSa YOUR_COMMANDS &, in modo che il comando venga avviato in background, altrimenti, se il comando impiega più di una frazione di secondo, ritarderà l'avvio successivo. Quindi con X = 2 e Y = 30, se il comando impiega 10 secondi, verrà avviato al minuto e poi 40 secondi dopo, anziché 30. Kudus a @paxdiablo.
Guss,

Per qualche motivo, se ometto la /bin/bash -cparte (comprese le virgolette dell'argomento) lo script viene eseguito solo ogni minuto, ignorando l'iterazione (nel mio caso X=12e Y=5).
abiyi,

3

Il lavoro Crontab può essere utilizzato per pianificare un lavoro in minuti / ore / giorni, ma non in secondi. L'alternativa :

Crea uno script da eseguire ogni 30 secondi:

#!/bin/bash
# 30sec.sh

for COUNT in `seq 29` ; do
  cp /application/tmp/* /home/test
  sleep 30
done

Utilizzare crontab -ee un crontab per eseguire questo script:

* * * * * /home/test/30sec.sh > /dev/null

2
se lo capisco correttamente, questo script viene eseguito 30 volte e attende 30 secondi tra ogni iterazione. Come ha senso eseguirlo ogni minuto in cron?
FuzzyAmi,

2

È possibile eseguire quello script come servizio, riavviare ogni 30 secondi

Registra un servizio

sudo vim /etc/systemd/system/YOUR_SERVICE_NAME.service

Incolla il comando seguente

Description=GIVE_YOUR_SERVICE_A_DESCRIPTION

Wants=network.target
After=syslog.target network-online.target

[Service]
Type=simple
ExecStart=YOUR_COMMAND_HERE
Restart=always
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target

Ricarica i servizi

sudo systemctl daemon-reload

Abilita il servizio

sudo systemctl enable YOUR_SERVICE_NAME

Avvia il servizio

sudo systemctl start YOUR_SERVICE_NAME

Controlla lo stato del tuo servizio

systemctl status YOUR_SERVICE_NAME

1

Grazie per tutte le buone risposte. Per semplificare, mi è piaciuta la soluzione mista, con il controllo su crontab e la divisione del tempo sullo script. Questo è ciò che ho fatto per eseguire uno script ogni 20 secondi (tre volte al minuto). Linea Crontab:

 * * * * 1-6 ./a/b/checkAgendaScript >> /home/a/b/cronlogs/checkAgenda.log

script:

cd /home/a/b/checkAgenda

java -jar checkAgenda.jar
sleep 20
java -jar checkAgenda.jar 
sleep 20
java -jar checkAgenda.jar 

cosa rappresenta 1-6?
Phantom007,

@ Phantom007 1-6 rappresenta dal lunedì al sabato, dove "-" è un intervallo e "0" è domenica. Ecco un buon link che spiega molto bene tutti i campi e dove puoi testarlo: " crontab.guru/# * _ * _ * _ * _ 1-6"
jfajunior

1

scrivere uno script di shell creare file .sh

nano every30second.sh

e scrivere la sceneggiatura

#!/bin/bash
For  (( i=1; i <= 2; i++ ))
do
    write Command here
    sleep 30
done

quindi imposta cron per questo script crontab -e

(* * * * * /home/username/every30second.sh)

questo cron call file .sh ogni 1 minuto e nel comando .sh file viene eseguito 2 volte in 1 minuto

se vuoi eseguire lo script per 5 secondi, sostituisci 30 per 5 e cambia per il ciclo in questo modo: For (( i=1; i <= 12; i++ ))

quando selezioni per un secondo, calcola 60 / secondo e scrivi in ​​For loop


0

Ho appena avuto un compito simile da fare e utilizzare il seguente approccio:

nohup watch -n30 "kill -3 NODE_PID" &

Avevo bisogno di un kill periodico -3 (per ottenere la traccia dello stack di un programma) ogni 30 secondi per diverse ore.

nohup ... & 

Questo è qui per essere sicuro che non perdo l'esecuzione di watch se perdo la shell (problema di rete, crash di Windows ecc ...)


0

Dai un'occhiata a frequ-cron : è vecchio ma molto stabile e puoi passare ai micro-secondi. A questo punto, l'unica cosa che vorrei dire è che sto ancora cercando di capire come installarlo al di fuori di init.d ma come un servizio di sistema nativo, ma sicuramente fino a Ubuntu 18 funziona solo bene ancora usando init.d (la distanza può variare nelle ultime versioni). Ha il vantaggio aggiuntivo (?) Di garantire che non genererà un'altra istanza dello script PHP a meno che non sia stata completata una precedente, il che riduce i potenziali problemi di perdita di memoria.


-1

Esegui in un ciclo di shell, ad esempio:

#!/bin/sh    
counter=1
while true ; do
 echo $counter
 counter=$((counter+1))
 if [[ "$counter" -eq 60 ]]; then
  counter=0
 fi
 wget -q http://localhost/tool/heartbeat/ -O - > /dev/null 2>&1 &
 sleep 1
done

Anche supponendo che 60dovrebbe essere una 30, si potrebbe desiderare di spostare che wget all'interno della ifdichiarazione, in caso contrario è eseguito ogni secondo. In ogni caso, non sono sicuro di come sia meglio di un singolo sleep 30. Se stavi monitorando l'ora UNIX effettiva piuttosto che il tuo contatore, farebbe la differenza.
paxdiablo,

echo la stampa del contatore, puoi scoprire il tempo ritardato da EXCUTE COMMAND se NON esegui wget in background.
Lo Vega,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.