Git Product home page Git Product logo

synology-monitoring's Introduction

synology-monitoring

Simple shell script for capturing SNMP stats to InfluxDB and monitoring health in Grafana.

Grafana Dashboard

Requirements
  • InfluxDB
  • Grafana
  • Synology NAS with SNMP enabled
Setup

The script is best suited to run on the NAS itself and highly recommended. It can be ran on another device but the device would need to be able to run shell scripts, able to make SNMP calls such as using snmpwalk and has the Synology SNMP MIB's . With minimal configuration, the Synology NAS should be more than capable of capturing this itself.

  1. Save the script to a known location on the NAS. For example, /volume1/Local/Scripts/synology_snmp.sh
  2. Input the InfluxDB information such as URL/IP, ports, database name, username and password.
  3. Modify any other configuration settings, each should have an explanation.
  4. On the Synology NAS, Select Control Panel > Terminal & SNMP > SNMP and enable SNMP V1, V2c service.
    Ensure that you set the community to 'public' in the Synology settings as the snmpwalk command is utilizing the -c public flag
  5. On the Synology NAS, Select Control Panel > Task Scheduler > Create >> Scheduled Task >> User-defined Script.
  6. Give the Task a recognizable name.
  7. On the Task Settings, set the Run command to bash /path/to/synology_snmp.sh
  8. On the Schedule:
    • Run on the following days: Daily
    • First run time: 00:00
    • Frequency: Every minute The frequency of times it runs within that minute is defined in the script itself
    • Last run time: 23:59
  9. Import the Synology_dashboard.json file to your Grafana instance, Create > Import > Import .json file.

synology-monitoring's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

synology-monitoring's Issues

line 79 syntax error

Hi, I tried out your script but got a syntax error in line 79 (and 125):

synology_snmp.sh: line 79: syntax error near unexpected token `<'

synology_snmp.sh: line 79: ` done < <(snmpwalk -c public -v 2c $nas_url 1.3.6.1.4.1.6574.1.5) #Parent OID for SYNOLOGY-SYSTEM-MIB'

I tried to get the answer from some general posts about syntax error in bash scripts, but couldn't find a solution.

Could you maybe help me out?

Thanks

[Question] getting an error when running via cmdline but it works fine via syno job

First off, the script works wonderfully. I love it and it's been great. It's more reliable than hoping that Telegraf loads the correct MIBs (it doesnt).

I just had a question. It works fine if I just leave it on the syno to run by itself, but if I try to run it myself, then I get this error:

> sudo sh synology_snmp.sh
Capturing 3 times
synology_snmp.sh: line 80: syntax error near unexpected token `('
synology_snmp.sh: line 80: `            done < <(snmpwalk -c public -v 2c $nas_url 1.3.6.1.4.1.6574.1.5) #Parent OID for SYNOLOGY-SYSTEM-MIB'

I'm not familiar with the "while; do, done < <" syntax. What is this error telling me? Why can't I run this via command line?

Specifically I'm trying to add in the MIB call for the UPS device model which is missing from your shell script.

edit: Needed to do "bash" in front of the script, duh

{"error":"database is required"}

This has been my favorite Synology Grafana dashboard by far.

I want to report an issue I did find while getting everything setup. (Possible fix at the end)

The script 'synology_snmp.sh' was not populating any data into the InfluxDB even after updating all the required fields.

Running the script manually from an SSH session showed the following error:
{"error":"database is required"}

The database name field was filed out correctly, and I was able to see the database from the database server itself.

I did notice something in the CURL commands though:
curl -i -XPOST "http://$influxdb_host:$influxdb_port/write?u=$influxdb_user&p=$influxdb_pass&db=$influxdb_name" --data-binary "$post_url"

What fixed the issue was moving the $influxdb_name before the username and password call:
curl -i -XPOST "http://$influxdb_host:$influxdb_port/write?db=$influxdb_name&u=$influxdb_user&p=$influxdb_pass" --data-binary "$post_url"

After this change, the errors went away and the data started to populate the database as expected.
InfluxDB shell version: 1.7.10

Annotation query failed

Followed the instructions in the 'Read Me', which were very clear.
Unfortunately there are no data shown in my dashboard.
Regularly an error message pops up saying 'Annotation query failed - Query missing in annotation definition'.

I can see the variables in Grafana (Synology Docker), so there is a link between Synology (actually Xpenology self-built, DSM 5.2) and the InfluxDB running as a Synology-Docker.

Somewhere in the read-me there is a reference to the MIB files. Do I need to do anything with them?

I also checked with the Paessler SNMP tester tool and as far as I can see all works fine.

Anyone an idea what I did wrong?

Grafana Data doesn't load

Using defaults and installing the grafana dashboard doesn't load any data. Script is running as described in the README.md. Grafana and influx are installed, and working for a different setup, new database is created, prior to adding using default values from script.

$'\r': command not found

Hi, I enabled SNMP Services and set community to public.

After running the script, i get this error:
/volume1/homes/jan/grafana.sh: line 2: $'\r': command not found
/volume1/homes/jan/grafana.sh: line 5: $'\r': command not found
/volume1/homes/jan/grafana.sh: line 15: $'\r': command not found
/volume1/homes/jan/grafana.sh: line 18: $'\r': command not found
/volume1/homes/jan/grafana.sh: line 23: $'\r': command not found
/volume1/homes/jan/grafana.sh: line 30: $'\r': command not found
/volume1/homes/jan/grafana.sh: line 69: syntax error near unexpected token elif' /volume1/homes/jan/grafana.sh: line 69: elif [[ $line == SYNOLOGY-SYSTEM-MIB::upgradeAvailable.0* ]]; then'

Theres no content in the InfluxDB

Templating init failed Datasource named Holocron was not found

After importing the json file, i get the error Templating init failed Datasource named Holocron was not found and there's no data. I tried creating a dashboard from scratch and was able to pull data from influxdb so i'm guessing there's something my Grafana doesn't like from your template. Any ideas on how to fix are appreciated!

{"error":"partial write: unable to parse ...

Related to Issue #13

I am still getting this issue when I include disk in the capture. I checked the above lines in the script and they already match what is listed as the fix. I have 14 disks (2 are for cache) in my enclosure

I get this error when trying to collect Raid or Disk info.

HTTP/1.1 400 Bad Request
Content-Type: application/json
Request-Id: 4774141a-3a4b-11eb-9423-0242ac110008
X-Influxdb-Build: OSS
X-Influxdb-Error: partial write: unable to parse 'synology_raid,nas_name=NAS,raid_name=Volume1 raid_status=,raid_free_size=,raid_total_size=': missing field value unable to parse 'synology_raid,nas_name=NAS,raid_name=StoragePool1 raid_status=,raid_free_size=,raid_total_size=': missing field value unable to parse 'synology_disk,nas_name=NAS,disk_name=Drive2 disk_model=SYNOLOGY-DISK-MIB::diskModel.13=STRING:"WDCWDS500G2B0C-00PXH0",disk_type=SYNOLOGY-DISK-MIB::diskType.13 = STRING: "SSD",disk_temp=SYNOLOGY-DISK-MIB::diskTemperature.13 = INTEGER: 41,disk_status=SYNOLOGY-DISK-MIB::diskStatus.13 = INTEGER: 1': invalid boolean dropped=0
X-Influxdb-Version: 1.8.3
X-Request-Id: 4774141a-3a4b-11eb-9423-0242ac110008
Date: Wed, 09 Dec 2020 18:20:56 GMT
Content-Length: 665

{"error":"partial write: unable to parse 'synology_raid,nas_name=NAS,raid_name=Volume1 raid_status=,raid_free_size=,raid_total_size=': missing field value\nunable to parse 'synology_raid,nas_name=NAS,raid_name=StoragePool1 raid_status=,raid_free_size=,raid_total_size=': missing field value\nunable to parse 'synology_disk,nas_name=NAS,disk_name=Drive2 disk_model=SYNOLOGY-DISK-MIB::diskModel.13=STRING:\"WDCWDS500G2B0C-00PXH0\",disk_type=SYNOLOGY-DISK-MIB::diskType.13 = STRING: \"SSD\",disk_temp=SYNOLOGY-DISK-MIB::diskTemperature.13 = INTEGER: 41,disk_status=SYNOLOGY-DISK-MIB::diskStatus.13 = INTEGER: 1': invalid boolean dropped=0"}

Syntax issues

/volume1/MEDIA/Scripts/synology_snmp.sh: line 69: syntax error near unexpected token elif' /volume1/MEDIA/Scripts/synology_snmp.sh: line 69: elif [[ $line == SYNOLOGY-SYSTEM-MIB::upgradeAvailable.0* ]]; then

This repeats on all elif lines

{"error":"partial write: unable to parse ...

Hi, thanks for making this script! I'm seeing an error when it runs on my DS1817+:

HTTP/1.1 100 Continue

HTTP/1.1 400 Bad Request
Content-Type: application/json
Request-Id: c13d3199-9308-11ea-a405-0242ac140004
X-Influxdb-Build: OSS
X-Influxdb-Error: partial write: unable to parse 'synology_disk,nas_name=nas01,disk_path=/dev/sdb disk_reads=SYNOLOGY-STORAGEIO-MIB::storageIONReadX.10 = Counter64: 44377368745984,disk_writes=SYNOLOGY-STORAGEIO-MIB::storageIONWrittenX.10 = Counter64: 3426853316608,disk_load=SYNOLOGY-STORAGEIO-MIB::storageIOLA.10 = INTEGER: 4': invalid boolean dropped=0
X-Influxdb-Version: 1.8.0
X-Request-Id: c13d3199-9308-11ea-a405-0242ac140004
Date: Sun, 10 May 2020 21:53:59 GMT
Content-Length: 349

{"error":"partial write: unable to parse 'synology_disk,nas_name=nas01,disk_path=/dev/sdb disk_reads=SYNOLOGY-STORAGEIO-MIB::storageIONReadX.10 = Counter64: 44377368745984,disk_writes=SYNOLOGY-STORAGEIO-MIB::storageIONWrittenX.10 = Counter64: 3426853316608,disk_load=SYNOLOGY-STORAGEIO-MIB::storageIOLA.10 = INTEGER: 4': invalid boolean dropped=0"}

Any idea what I've done wrong or what I can do to correct it? Thanks!

Improving the readme

Hey ๐Ÿ‘‹ thanks for the job

I got few questions, how did you install InfluxDB ? With Docker ?
Could you add some details in the readme for the global setup of influxdb, grafana and your script ?

Thank you in advance

Error, when UPS is active

{"error":"partial write: unable to parse 'synology_ups,nas_name=xxxx,ups_group=NAS ups_status=OB,ups_load=10,ups_battery_runtime=2085,ups_battery_charge=82': invalid boolean dropped=0"}

I don't know what the status OB means, but it happens, when I cut the power and the NAS is running on the battery of UPS. I guess the script looks only for 0 and 1.

still references to Holocron

Line 1650 - Still says Holocron.

Also would it not be better to change all the "Defaults" to "InfluxDB" which is the name the influxDB connection gets as a default when connecting to Grafana.

Else call out in your readme that you need to rename your influxBD to "Default".

Thanks for the work by the way - not got it fully working yet, but they're the issues I've had so far.

[Bug] When having 12+ drives, the regex's don't capture properly

Specifically, Drive 2, which comes in with $id=1, get overwritten by Drive 12 which comes in with $id=11

Output capture form running the script. You can see that for Disk 2, it actually captures the id.11 entry because its only grabbing the first digit, not both, but then it fails because later regexs capture the 11 properly and now the id doesnt match and so the final output fails, is my guess.

synology_disk,nas_name=Omoikane,disk_name=Drive1 disk_model="SSD840EVO250GB",disk_type="SSD",disk_temp=30,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive2 disk_model=SYNOLOGY-DISK-MIB::diskModel.11=STRING:"HUH728080ALE600",disk_type=SYNOLOGY-DISK-MIB::diskType.11 = STRING: "SATA",disk_temp=SYNOLOGY-DISK-MIB::diskTemperature.11 = INTEGER: 39,disk_status=SYNOLOGY-DISK-MIB::diskStatus.11 = INTEGER: 1
synology_disk,nas_name=Omoikane,disk_name=Drive3 disk_model="HUH721212ALE600",disk_type="SATA",disk_temp=39,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive4 disk_model="HDN726040ALE614",disk_type="SATA",disk_temp=40,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive5 disk_model="SSD840EVO250GB",disk_type="SSD",disk_temp=35,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive6 disk_model="HDN726040ALE614",disk_type="SATA",disk_temp=39,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive7 disk_model="HUH721212ALE600",disk_type="SATA",disk_temp=39,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive8 disk_model="HUH728080ALE600",disk_type="SATA",disk_temp=39,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive9 disk_model="HDN724040ALE640",disk_type="SATA",disk_temp=35,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive10 disk_model="HUH728080ALE604",disk_type="SATA",disk_temp=36,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive11 disk_model="HUH728080ALE600",disk_type="SATA",disk_temp=38,disk_status=1
synology_disk,nas_name=Omoikane,disk_name=Drive12 disk_model="HUH728080ALE600",disk_type="SATA",disk_temp=39,disk_status=1

Running the SNMP walk commands manually generates the correct result

Omoikane:/volume1/scripts$ snmpwalk -v 2c -c public localhost SYNOLOGY-DISK-MIB::diskID
SYNOLOGY-DISK-MIB::diskID.0 = STRING: "Drive 1"
SYNOLOGY-DISK-MIB::diskID.1 = STRING: "Drive 2"
SYNOLOGY-DISK-MIB::diskID.2 = STRING: "Drive 3"
SYNOLOGY-DISK-MIB::diskID.3 = STRING: "Drive 4"
SYNOLOGY-DISK-MIB::diskID.4 = STRING: "Drive 5"
SYNOLOGY-DISK-MIB::diskID.5 = STRING: "Drive 6"
SYNOLOGY-DISK-MIB::diskID.6 = STRING: "Drive 7"
SYNOLOGY-DISK-MIB::diskID.7 = STRING: "Drive 8"
SYNOLOGY-DISK-MIB::diskID.8 = STRING: "Drive 9"
SYNOLOGY-DISK-MIB::diskID.9 = STRING: "Drive 10"
SYNOLOGY-DISK-MIB::diskID.10 = STRING: "Drive 11"
SYNOLOGY-DISK-MIB::diskID.11 = STRING: "Drive 12"
Omoikane:/volume1/scripts$ snmpwalk -v 2c -c public localhost SYNOLOGY-DISK-MIB::diskModel
SYNOLOGY-DISK-MIB::diskModel.0 = STRING: "SSD 840 EVO 250GB       "
SYNOLOGY-DISK-MIB::diskModel.1 = STRING: "HDN724040ALE640         "
SYNOLOGY-DISK-MIB::diskModel.2 = STRING: "HUH721212ALE600         "
SYNOLOGY-DISK-MIB::diskModel.3 = STRING: "HDN726040ALE614         "
SYNOLOGY-DISK-MIB::diskModel.4 = STRING: "SSD 840 EVO 250GB       "
SYNOLOGY-DISK-MIB::diskModel.5 = STRING: "HDN726040ALE614         "
SYNOLOGY-DISK-MIB::diskModel.6 = STRING: "HUH721212ALE600         "
SYNOLOGY-DISK-MIB::diskModel.7 = STRING: "HUH728080ALE600         "
SYNOLOGY-DISK-MIB::diskModel.8 = STRING: "HDN724040ALE640         "
SYNOLOGY-DISK-MIB::diskModel.9 = STRING: "HUH728080ALE604         "
SYNOLOGY-DISK-MIB::diskModel.10 = STRING: "HUH728080ALE600         "
SYNOLOGY-DISK-MIB::diskModel.11 = STRING: "HUH728080ALE600         "
Omoikane:/volume1/scripts$ snmpwalk -v 2c -c public localhost SYNOLOGY-DISK-MIB::diskType
SYNOLOGY-DISK-MIB::diskType.0 = STRING: "SSD"
SYNOLOGY-DISK-MIB::diskType.1 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.2 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.3 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.4 = STRING: "SSD"
SYNOLOGY-DISK-MIB::diskType.5 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.6 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.7 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.8 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.9 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.10 = STRING: "SATA"
SYNOLOGY-DISK-MIB::diskType.11 = STRING: "SATA"
Omoikane:/volume1/scripts$ snmpwalk -v 2c -c public localhost SYNOLOGY-DISK-MIB::diskTemperature
SYNOLOGY-DISK-MIB::diskTemperature.0 = INTEGER: 29
SYNOLOGY-DISK-MIB::diskTemperature.1 = INTEGER: 36
SYNOLOGY-DISK-MIB::diskTemperature.2 = INTEGER: 39
SYNOLOGY-DISK-MIB::diskTemperature.3 = INTEGER: 40
SYNOLOGY-DISK-MIB::diskTemperature.4 = INTEGER: 35
SYNOLOGY-DISK-MIB::diskTemperature.5 = INTEGER: 39
SYNOLOGY-DISK-MIB::diskTemperature.6 = INTEGER: 39
SYNOLOGY-DISK-MIB::diskTemperature.7 = INTEGER: 39
SYNOLOGY-DISK-MIB::diskTemperature.8 = INTEGER: 35
SYNOLOGY-DISK-MIB::diskTemperature.9 = INTEGER: 36
SYNOLOGY-DISK-MIB::diskTemperature.10 = INTEGER: 38
SYNOLOGY-DISK-MIB::diskTemperature.11 = INTEGER: 39
Omoikane:/volume1/scripts$ snmpwalk -v 2c -c public localhost SYNOLOGY-DISK-MIB::diskStatus
SYNOLOGY-DISK-MIB::diskStatus.0 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.1 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.2 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.3 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.4 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.5 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.6 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.7 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.8 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.9 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.10 = INTEGER: 1
SYNOLOGY-DISK-MIB::diskStatus.11 = INTEGER: 1

I suspect these lines are not capturing the regexs properly, specifically the $id*, will play around with it

247 if [[ $line == SYNOLOGY-DISK-MIB::diskModel.$id* ]]; then
248 disk_model=${line/"SYNOLOGY-DISK-MIB::diskModel."$id" = STRING: "/}; disk_model=${disk_model// /}
249 fi
250 
251 if [[ $line == SYNOLOGY-DISK-MIB::diskType.$id* ]]; then
252 disk_type=${line/"SYNOLOGY-DISK-MIB::diskType."$id" = STRING: "/}
253 fi
254 
255 if [[ $line == SYNOLOGY-DISK-MIB::diskStatus.$id* ]]; then
256 disk_status=${line/"SYNOLOGY-DISK-MIB::diskStatus."$id" = INTEGER: "/}
257 fi
258 
259 if [[ $line == SYNOLOGY-DISK-MIB::diskTemperature.$id* ]]; then
260 disk_temp=${line/"SYNOLOGY-DISK-MIB::diskTemperature."$id" = INTEGER: "/}
261 fi

question on script error

I'm getting "timeout: no response from xxxxx" I get the same from localhost or if I input the ip address. Any ideas why the script won't run or how to troubleshoot?

I'd love to test this out but i don't see why the script can't get a response from localhost....

HTTP/1.1 204 No Content

After much tinkering and tweaking, I finally got this running, but I keep getting this HTTP/1.1 204 No Content error, and I'm not entirely sure why. It's by no means a deal breaker, and I can't even tell what's wrong, but I know it'll keep me up at night. Hoping you can chime in and tell me what's going on. Thanks in advance!

Capturing 3 times
Skipping UPS capture
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  1898    0     0  100  1898      0   4466 --:--:-- --:--:-- --:--:--  4476
HTTP/1.1 100 Continue

HTTP/1.1 204 No Content
Content-Type: application/json
Request-Id: 881972bb-1a20-11ea-8170-0242ac110002
X-Influxdb-Build: OSS
X-Influxdb-Version: 1.7.9
X-Request-Id: 881972bb-1a20-11ea-8170-0242ac110002
Date: Mon, 09 Dec 2019 01:09:21 GMT

Database name Default was not found

Hi,

First thank you for this script. I have a problem.
Indeed it doesn't seems to to find the datas in the database but I don't know where the problem is from.
I'm sure about the path of my script in my task scheduler even if I have no results when I check the results via the task scheduler and I'm sure about the infos I putted in the script.sh.
What to I put in community? public?

Thank you in advance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.