diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0d7f635e28d544f806c187fb8573c22c85cf7ad..777eefbdfd1eef437c75bbcc31810e8aea616637 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ It is highly recommended that all users migrate to the new riemann output plugin
 - [#2179](https://github.com/influxdata/telegraf/pull/2179): Added more InnoDB metric to MySQL plugin.
 - [#2251](https://github.com/influxdata/telegraf/pull/2251): InfluxDB output: use own client for improved through-put and less allocations.
 - [#1900](https://github.com/influxdata/telegraf/pull/1900): Riemann plugin rewrite.
+- [#1453](https://github.com/influxdata/telegraf/pull/1453): diskio: add support for name templates and udev tags.
 
 ### Bugfixes
 
diff --git a/plugins/inputs/system/disk.go b/plugins/inputs/system/disk.go
index 548a9ce23efc562df7d84a303248ce33d65c026d..3f6d83c1cc592f93f3484d321b6455df8d5e74cd 100644
--- a/plugins/inputs/system/disk.go
+++ b/plugins/inputs/system/disk.go
@@ -2,6 +2,7 @@ package system
 
 import (
 	"fmt"
+	"regexp"
 	"strings"
 
 	"github.com/influxdata/telegraf"
@@ -82,7 +83,11 @@ type DiskIOStats struct {
 	ps PS
 
 	Devices          []string
+	DeviceTags       []string
+	NameTemplates    []string
 	SkipSerialNumber bool
+
+	infoCache map[string]diskInfoCache
 }
 
 func (_ *DiskIOStats) Description() string {
@@ -96,6 +101,23 @@ var diskIoSampleConfig = `
   # devices = ["sda", "sdb"]
   ## Uncomment the following line if you need disk serial numbers.
   # skip_serial_number = false
+  #
+  ## On systems which support it, device metadata can be added in the form of
+  ## tags.
+  ## Currently only Linux is supported via udev properties. You can view
+  ## available properties for a device by running:
+  ## 'udevadm info -q property -n /dev/sda'
+  # device_tags = ["ID_FS_TYPE", "ID_FS_USAGE"]
+  #
+  ## Using the same metadata source as device_tags, you can also customize the
+  ## name of the device via templates.
+  ## The 'name_templates' parameter is a list of templates to try and apply to
+  ## the device. The template may contain variables in the form of '$PROPERTY' or
+  ## '${PROPERTY}'. The first template which does not contain any variables not
+  ## present for the device is used as the device name tag.
+  ## The typical use case is for LVM volumes, to get the VG/LV name instead of
+  ## the near-meaningless DM-0 name.
+  # name_templates = ["$ID_FS_LABEL","$DM_VG_NAME/$DM_LV_NAME"]
 `
 
 func (_ *DiskIOStats) SampleConfig() string {
@@ -123,7 +145,10 @@ func (s *DiskIOStats) Gather(acc telegraf.Accumulator) error {
 			continue
 		}
 		tags := map[string]string{}
-		tags["name"] = io.Name
+		tags["name"] = s.diskName(io.Name)
+		for t, v := range s.diskTags(io.Name) {
+			tags[t] = v
+		}
 		if !s.SkipSerialNumber {
 			if len(io.SerialNumber) != 0 {
 				tags["serial"] = io.SerialNumber
@@ -148,6 +173,64 @@ func (s *DiskIOStats) Gather(acc telegraf.Accumulator) error {
 	return nil
 }
 
+var varRegex = regexp.MustCompile(`\$(?:\w+|\{\w+\})`)
+
+func (s *DiskIOStats) diskName(devName string) string {
+	di, err := s.diskInfo(devName)
+	if err != nil {
+		// discard error :-(
+		// We can't return error because it's non-fatal to the Gather().
+		// And we have no logger, so we can't log it.
+		return devName
+	}
+	if di == nil {
+		return devName
+	}
+
+	for _, nt := range s.NameTemplates {
+		miss := false
+		name := varRegex.ReplaceAllStringFunc(nt, func(sub string) string {
+			sub = sub[1:] // strip leading '$'
+			if sub[0] == '{' {
+				sub = sub[1 : len(sub)-1] // strip leading & trailing '{' '}'
+			}
+			if v, ok := di[sub]; ok {
+				return v
+			}
+			miss = true
+			return ""
+		})
+
+		if !miss {
+			return name
+		}
+	}
+
+	return devName
+}
+
+func (s *DiskIOStats) diskTags(devName string) map[string]string {
+	di, err := s.diskInfo(devName)
+	if err != nil {
+		// discard error :-(
+		// We can't return error because it's non-fatal to the Gather().
+		// And we have no logger, so we can't log it.
+		return nil
+	}
+	if di == nil {
+		return nil
+	}
+
+	tags := map[string]string{}
+	for _, dt := range s.DeviceTags {
+		if v, ok := di[dt]; ok {
+			tags[dt] = v
+		}
+	}
+
+	return tags
+}
+
 func init() {
 	inputs.Add("disk", func() telegraf.Input {
 		return &DiskStats{ps: &systemPS{}}
diff --git a/plugins/inputs/system/disk_linux.go b/plugins/inputs/system/disk_linux.go
new file mode 100644
index 0000000000000000000000000000000000000000..e5a0cff55437adf9e2b6242230db1a42203b7551
--- /dev/null
+++ b/plugins/inputs/system/disk_linux.go
@@ -0,0 +1,66 @@
+package system
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+	"syscall"
+)
+
+type diskInfoCache struct {
+	stat   syscall.Stat_t
+	values map[string]string
+}
+
+var udevPath = "/run/udev/data"
+
+func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
+	fi, err := os.Stat("/dev/" + devName)
+	if err != nil {
+		return nil, err
+	}
+	stat, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		return nil, nil
+	}
+
+	if s.infoCache == nil {
+		s.infoCache = map[string]diskInfoCache{}
+	}
+	ic, ok := s.infoCache[devName]
+	if ok {
+		return ic.values, nil
+	} else {
+		ic = diskInfoCache{
+			stat:   *stat,
+			values: map[string]string{},
+		}
+		s.infoCache[devName] = ic
+	}
+	di := ic.values
+
+	major := stat.Rdev >> 8 & 0xff
+	minor := stat.Rdev & 0xff
+
+	f, err := os.Open(fmt.Sprintf("%s/b%d:%d", udevPath, major, minor))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	scnr := bufio.NewScanner(f)
+
+	for scnr.Scan() {
+		l := scnr.Text()
+		if len(l) < 4 || l[:2] != "E:" {
+			continue
+		}
+		kv := strings.SplitN(l[2:], "=", 2)
+		if len(kv) < 2 {
+			continue
+		}
+		di[kv[0]] = kv[1]
+	}
+
+	return di, nil
+}
diff --git a/plugins/inputs/system/disk_linux_test.go b/plugins/inputs/system/disk_linux_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..801ad328a2657f4e81d5909e27a8990a109479fb
--- /dev/null
+++ b/plugins/inputs/system/disk_linux_test.go
@@ -0,0 +1,101 @@
+// +build linux
+
+package system
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+var nullDiskInfo = []byte(`
+E:MY_PARAM_1=myval1
+E:MY_PARAM_2=myval2
+`)
+
+// setupNullDisk sets up fake udev info as if /dev/null were a disk.
+func setupNullDisk(t *testing.T) func() error {
+	td, err := ioutil.TempDir("", ".telegraf.TestDiskInfo")
+	require.NoError(t, err)
+
+	origUdevPath := udevPath
+
+	cleanFunc := func() error {
+		udevPath = origUdevPath
+		return os.RemoveAll(td)
+	}
+
+	udevPath = td
+	err = ioutil.WriteFile(td+"/b1:3", nullDiskInfo, 0644) // 1:3 is the 'null' device
+	if err != nil {
+		cleanFunc()
+		t.Fatal(err)
+	}
+
+	return cleanFunc
+}
+
+func TestDiskInfo(t *testing.T) {
+	clean := setupNullDisk(t)
+	defer clean()
+
+	s := &DiskIOStats{}
+	di, err := s.diskInfo("null")
+	require.NoError(t, err)
+	assert.Equal(t, "myval1", di["MY_PARAM_1"])
+	assert.Equal(t, "myval2", di["MY_PARAM_2"])
+
+	// test that data is cached
+	err = clean()
+	require.NoError(t, err)
+
+	di, err = s.diskInfo("null")
+	require.NoError(t, err)
+	assert.Equal(t, "myval1", di["MY_PARAM_1"])
+	assert.Equal(t, "myval2", di["MY_PARAM_2"])
+
+	// unfortunately we can't adjust mtime on /dev/null to test cache invalidation
+}
+
+// DiskIOStats.diskName isn't a linux specific function, but dependent
+// functions are a no-op on non-Linux.
+func TestDiskIOStats_diskName(t *testing.T) {
+	defer setupNullDisk(t)()
+
+	tests := []struct {
+		templates []string
+		expected  string
+	}{
+		{[]string{"$MY_PARAM_1"}, "myval1"},
+		{[]string{"${MY_PARAM_1}"}, "myval1"},
+		{[]string{"x$MY_PARAM_1"}, "xmyval1"},
+		{[]string{"x${MY_PARAM_1}x"}, "xmyval1x"},
+		{[]string{"$MISSING", "$MY_PARAM_1"}, "myval1"},
+		{[]string{"$MY_PARAM_1", "$MY_PARAM_2"}, "myval1"},
+		{[]string{"$MISSING"}, "null"},
+		{[]string{"$MY_PARAM_1/$MY_PARAM_2"}, "myval1/myval2"},
+		{[]string{"$MY_PARAM_2/$MISSING"}, "null"},
+	}
+
+	for _, tc := range tests {
+		s := DiskIOStats{
+			NameTemplates: tc.templates,
+		}
+		assert.Equal(t, tc.expected, s.diskName("null"), "Templates: %#v", tc.templates)
+	}
+}
+
+// DiskIOStats.diskTags isn't a linux specific function, but dependent
+// functions are a no-op on non-Linux.
+func TestDiskIOStats_diskTags(t *testing.T) {
+	defer setupNullDisk(t)()
+
+	s := &DiskIOStats{
+		DeviceTags: []string{"MY_PARAM_2"},
+	}
+	dt := s.diskTags("null")
+	assert.Equal(t, map[string]string{"MY_PARAM_2": "myval2"}, dt)
+}
diff --git a/plugins/inputs/system/disk_other.go b/plugins/inputs/system/disk_other.go
new file mode 100644
index 0000000000000000000000000000000000000000..fa9121cdf41e6564c9c3ca30cc29542720afd69b
--- /dev/null
+++ b/plugins/inputs/system/disk_other.go
@@ -0,0 +1,9 @@
+// +build !linux
+
+package system
+
+type diskInfoCache struct{}
+
+func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
+	return nil, nil
+}