diff --git a/devices/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md b/devices/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md
new file mode 100644
index 0000000000000000000000000000000000000000..fb385afdd84f7018077a24d4901aaf3ca1a63356
--- /dev/null
+++ b/devices/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md
@@ -0,0 +1,222 @@
+# Test purpose
+
+Tango Controls Device Servers (DS) can automatically poll device attribute values. The default for the polling is a single thread that runs in the DS process. It has the purpose to call the `read` function of every attribute in all devices that run in the DS.
+The highest polling rate among all the attributes in all devices determines how often the polling thread runs. This can lead to a situation where the single polling thread is unable to finish executing all attribute `read` functions. When that happens, the attributes that did not have their `read` function executed will not have updated values. Since the polling thread always follows the same order of attributes, this will lead to some attribute values never be updated.
+
+We investigate whether using more polling threads can alleviate the situation for a single or multiple devices that run in the same DS.
+
+# Test set-up
+
+- Two devices run in the same DS.
+    - Tango DB is modified to have a DS named  `monitoring_performance_test/1`.
+    
+    - Tango DB is modified to have the DS named `monitoring_performance_test/1` run two devices named `test/monitoring_performance/1` and `test/monitoring_performance/2`. Both devices instantiate the same class `Monitoring_Performance_Device`.
+    
+    - Execute the DS like this: `bin/start-DS.sh devices/test/devices/automatic_polling_performance_test/monitoring_performance_test.py 1`
+    
+    - Get a DeviceProxy object to both devices like this:
+    
+        ```python
+        d1 = DeviceProxy('test/monitoring_performance/1')
+        d2 = DeviceProxy('test/monitoring_performance/2')
+        ```
+    
+        This will execute the device code and perform the automatic polling.
+    
+    - Devices in the appended data (section Data) are labelled d1 and d2.
+- Each device has 4 read-only attributes (MPs) that are arrays with 2e6 doubles.
+    - In Tango DB automatic polling every 1s is enabled for each array.
+- Two scenarios:
+    1. On read a random number gets generated and the entire array is populated with it. Populating the array with a new random number on every read access prevents caching.
+    2. A 0-filled array is created in init_device and copied to the attribute when read is called.
+- The number of polling threads is adjusted in init_device. The number of polling threads is a DS setting. Due to inconsistencies how Tango Controls handles input parameters, it is nor possible to pass parameters to devices.
+- Number of polling threads: 1, 10, 100
+
+# Test execution
+
+- The DS source code is modified for the number of polling threads according to the test set-up outlined above.
+- The DS is started manually.
+- The attribute polling resumes automatically as soon as the device state is set to ON in init_device.
+- The test script creates two Python processes that are assigned to one of the two devices each.
+    - The process creates a `DeviceProxy` object for its device and runs 
+    - The process executes `attribute_polling_stats` and the results are printed out.
+    - The process exits.
+- The DS is allowed to run for approximately 10 polling iterations.
+    - The DS processes will print statistics about the polling. 
+- The test is manually stopped and the output copied.
+
+Test results are in the attached file.
+
+# Findings
+
+The tests have shown that polling gets significantly delayed under certain circumstances:
+
+- When the read function takes a longer time to return an attribute's value.
+    Examples that have been tried out in this test but that are not excluding other reasons:
+    - Creating a numpy array of size 2e6 on the fly with the same random number in every value.
+    - Reading of a 2e5 values array from an OPC-UA server and converting it to a numpy array.
+
+From this finding other reasons that will have a negative impact on polling become immediately obvious and need to be avoided no matter what:
+
+- Fetching data on-the-fly over a slow communication link in an attribute's ` read` function.
+- Fetching a big amount of data on-the-fly over a fast communication link in an attribute's ` read` function. 
+- Computing data on-the-fly in an attribute's `read`function.
+
+Adding more polling threads to a DS does not alleviate this situation. The reason lies in the way how Tango Controls polling works. As a default the polling is performed by one thread per DS or, if the polling thread pool size is increased, by at most one thread per device. Adding twice as many polling threads as devices that are running in a DS does not change the situation as the data suggests.
+
+# Recommendation
+
+For devices that contain attributes with values that are big in byte size, i.e. arrays of significant size, it is strongly recommended to assess the situation, i.e. measure how long reading the data over a communication link takes. If it is essential to poll a high volume attribute at a polling rate that exceeds the performance capabilities of the DS's polling thread, several options are viable:
+
+- Distribute high volume attributes among separate devices which run in their own DS.
+    If necessary create more and more devices with less and less attributes until the desired polling rate can be accomplished. Even if this means that each high volume attribute exists in its own device. To Tango Controls or to device clients it does not matter if a device contains one or many attributes.
+- Distribute high volume attributes among separate devices but continue running them in the same DS. It is necessary to increase the number of polling threads to at least the number of devices in the DS.
+
+The two solutions above are mutually exclusive. Other solutions that alleviate the load on single polling threads can be added to either of the above as well:
+
+- Lower the polling rate so that the polling thread has more time to perform calling the attribute `read` functions. This means that an attribute's read function is allowed to take longer to perform its tasks. Note that this does not solve the original problem that the `read` function is just too slow when it is called.
+- Move the updating of attribute values to their own process. Then the read function can return the current value immediately because the value gets independently updated.
+
+Executive summary: even Tango Controls cannot perform magic. High volume attributes cannot be polled and infinite velocity. One has to distribute the polling load either over more DS, more threads within a DS or more processes within a DS.
+
+# Data
+
+Filling the array with the same newly created random number on ever read.
+
+threads = 1
+d1
+	iterations = 10
+
+	Polling duration
+	min = 0.7053400000000001[s]
+	max = 0.7174940000000001[s]
+	median = 0.7123280000000001[s]
+	mean = 0.7121181[s]
+	stddev = 0.004004320403014733[s]
+	
+	Polling delay
+	min = 0.792[s]
+	max = 2.207[s]
+	median = 0.8115[s]
+	mean = 1.2221[s]
+	stddev = 0.6406804897919087[s]
+d2
+	iterations = 10
+
+	Polling duration
+	min = 0.689903[s]
+	max = 0.715033[s]
+	median = 0.7069909999999999[s]
+	mean = 0.7061663000000001[s]
+	stddev = 0.00792590103458277[s]
+	
+	Polling delay
+	min = 0.744[s]
+	max = 2.245[s]
+	median = 0.758[s]
+	mean = 1.2010999999999998[s]
+	stddev = 0.681659805181441[s]
+
+threads = 10
+d1
+	iterations = 10
+
+	Polling duration
+	min = 0.700119[s]
+	max = 0.7102459999999999[s]
+	median = 0.710067[s]
+	mean = 0.7068808[s]
+	stddev = 0.004127314376201529[s]
+	
+	Polling delay
+	min = 0.802[s]
+	max = 2.196[s]
+	median = 0.806[s]
+	mean = 1.2213[s]
+	stddev = 0.6370044034384692[s]
+d2
+	iterations = 10
+
+	Polling duration
+	min = 0.6984130000000001[s]
+	max = 0.706296[s]
+	median = 0.7044239999999999[s]
+	mean = 0.7036658000000001[s]
+	stddev = 0.0025871636902213896[s]
+	
+	Polling delay
+	min = 0.758[s]
+	max = 2.24[s]
+	median = 0.759[s]
+	mean = 1.3504[s]
+	stddev = 0.7247257688256988[s]
+
+threads = 100
+d1
+	iterations = 10
+
+	Polling duration
+	min = 0.690158[s]
+	max = 0.720522[s]
+	median = 0.7119365[s]
+	mean = 0.7107762[s]
+	stddev = 0.008783150821886167[s]
+	
+	Polling delay
+	min = 0.79[s]
+	max = 2.209[s]
+	median = 0.8[s]
+	mean = 1.2176000000000002[s]
+	stddev = 0.6462041782594724[s]
+d2
+	iterations = 10
+
+	Polling duration
+	min = 0.702939[s]
+	max = 0.724869[s]
+	median = 0.7119840000000001[s]
+	mean = 0.7122735[s]
+	stddev = 0.006137572716473502[s]
+	
+	Polling delay
+	min = 0.749[s]
+	max = 2.25[s]
+	median = 0.755[s]
+	mean = 1.2005[s]
+	stddev = 0.6824934065615579[s]
+
+
+Returning a 0-filled array that was created in init_device
+threads = 100
+d1
+	iterations = 10
+
+	Polling duration
+	min = 0.005712[s]
+	max = 0.008997999999999999[s]
+	median = 0.0065065[s]
+	mean = 0.006732[s]
+	stddev = 0.0009050982267135427[s]
+	
+	Polling delay
+	min = 0.998[s]
+	max = 1.001[s]
+	median = 1.0[s]
+	mean = 0.9997[s]
+	stddev = 0.0007810249675906477[s]
+d2
+	iterations = 10
+
+	Polling duration
+	min = 0.0062759999999999995[s]
+	max = 0.008672000000000001[s]
+	median = 0.0069180000000000005[s]
+	mean = 0.0070902[s]
+	stddev = 0.0007260824746542229[s]
+	
+	Polling delay
+	min = 0.996[s]
+	max = 1.003[s]
+	median = 0.999[s]
+	mean = 0.9997[s]
+	stddev = 0.002491987158875375[s]
\ No newline at end of file