Add support for decimal timestamps to ts-epoch modifier (#3358)

This commit is contained in:
Daniel Nelson 2017-10-19 16:36:32 -07:00 committed by GitHub
parent 2ad9183e47
commit bb6d50f011
3 changed files with 108 additions and 6 deletions

View File

@ -100,7 +100,7 @@ current time.
- ts-rfc3339 ("2006-01-02T15:04:05Z07:00") - ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
- ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00") - ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
- ts-httpd ("02/Jan/2006:15:04:05 -0700") - ts-httpd ("02/Jan/2006:15:04:05 -0700")
- ts-epoch (seconds since unix epoch) - ts-epoch (seconds since unix epoch, may contain decimal)
- ts-epochnano (nanoseconds since unix epoch) - ts-epochnano (nanoseconds since unix epoch)
- ts-"CUSTOM" - ts-"CUSTOM"
@ -130,6 +130,19 @@ This example input and config parses a file using a custom timestamp conversion:
patterns = ['%{TIMESTAMP_ISO8601:timestamp:ts-"2006-01-02 15:04:05"} value=%{NUMBER:value:int}'] patterns = ['%{TIMESTAMP_ISO8601:timestamp:ts-"2006-01-02 15:04:05"} value=%{NUMBER:value:int}']
``` ```
This example input and config parses a file using a timestamp in unix time:
```
1466004605 value=42
1466004605.123456789 value=42
```
```toml
[[inputs.logparser]]
[inputs.logparser.grok]
patterns = ['%{NUMBER:timestamp:ts-epoch} value=%{NUMBER:value:int}']
```
This example parses a file using a built-in conversion and a custom pattern: This example parses a file using a built-in conversion and a custom pattern:
``` ```

View File

@ -253,12 +253,30 @@ func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
case STRING: case STRING:
fields[k] = strings.Trim(v, `"`) fields[k] = strings.Trim(v, `"`)
case EPOCH: case EPOCH:
iv, err := strconv.ParseInt(v, 10, 64) parts := strings.SplitN(v, ".", 2)
if err != nil { if len(parts) == 0 {
log.Printf("E! Error parsing %s to int: %s", v, err) log.Printf("E! Error parsing %s to timestamp: %s", v, err)
} else { break
timestamp = time.Unix(iv, 0)
} }
sec, err := strconv.ParseInt(parts[0], 10, 64)
if err != nil {
log.Printf("E! Error parsing %s to timestamp: %s", v, err)
break
}
ts := time.Unix(sec, 0)
if len(parts) == 2 {
padded := fmt.Sprintf("%-9s", parts[1])
nsString := strings.Replace(padded[:9], " ", "0", -1)
nanosec, err := strconv.ParseInt(nsString, 10, 64)
if err != nil {
log.Printf("E! Error parsing %s to timestamp: %s", v, err)
break
}
ts = ts.Add(time.Duration(nanosec) * time.Nanosecond)
}
timestamp = ts
case EPOCH_NANO: case EPOCH_NANO:
iv, err := strconv.ParseInt(v, 10, 64) iv, err := strconv.ParseInt(v, 10, 64)
if err != nil { if err != nil {

View File

@ -385,6 +385,77 @@ func TestParseEpoch(t *testing.T) {
assert.Equal(t, time.Unix(1466004605, 0), metricA.Time()) assert.Equal(t, time.Unix(1466004605, 0), metricA.Time())
} }
func TestParseEpochDecimal(t *testing.T) {
var tests = []struct {
name string
line string
noMatch bool
err error
tags map[string]string
fields map[string]interface{}
time time.Time
}{
{
name: "ns precision",
line: "1466004605.359052000 value=42",
tags: map[string]string{},
fields: map[string]interface{}{
"value": int64(42),
},
time: time.Unix(0, 1466004605359052000),
},
{
name: "ms precision",
line: "1466004605.359 value=42",
tags: map[string]string{},
fields: map[string]interface{}{
"value": int64(42),
},
time: time.Unix(0, 1466004605359000000),
},
{
name: "second precision",
line: "1466004605 value=42",
tags: map[string]string{},
fields: map[string]interface{}{
"value": int64(42),
},
time: time.Unix(0, 1466004605000000000),
},
{
name: "sub ns precision",
line: "1466004605.123456789123 value=42",
tags: map[string]string{},
fields: map[string]interface{}{
"value": int64(42),
},
time: time.Unix(0, 1466004605123456789),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := &Parser{
Patterns: []string{"%{NUMBER:ts:ts-epoch} value=%{NUMBER:value:int}"},
}
assert.NoError(t, parser.Compile())
m, err := parser.ParseLine(tt.line)
if tt.noMatch {
require.Nil(t, m)
require.Nil(t, err)
return
}
require.Equal(t, tt.err, err)
require.NotNil(t, m)
require.Equal(t, tt.tags, m.Tags())
require.Equal(t, tt.fields, m.Fields())
require.Equal(t, tt.time, m.Time())
})
}
}
func TestParseEpochErrors(t *testing.T) { func TestParseEpochErrors(t *testing.T) {
p := &Parser{ p := &Parser{
Patterns: []string{"%{MYAPP}"}, Patterns: []string{"%{MYAPP}"},