Skip to content

Commit e0605e9

Browse files
added application insights telemetry package to send metrics and logs (#421)
* added application insights telemetry package to send metrics and logs to appinsights * moved a function common to both packages acquired a lock before writing to file * added read write lock as per chandan comment. Addressed jaeryn comments * fixed telemetry unit test * defined interface and added appinsights package as vendor to acn * added vendor package.go(appinsights) * dependencies of appinsights * added AI dependencies * updated unit tests * addressed review comments
1 parent decd0d4 commit e0605e9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+5776
-104
lines changed

Gopkg.lock

Lines changed: 132 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
name = "github.com/containernetworking/cni"
6262
revision = "fbb95fff8a5239a4295c991efa8a397d43118f7e"
6363

64+
[[constraint]]
65+
name = "github.com/microsoft/ApplicationInsights-Go"
66+
revision = "d813d7725313000ad1b71627b8951323635f0572"
67+
6468
[prune]
6569
go-tests = true
6670
unused-packages = true

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ COREFILES = \
77
$(wildcard netlink/*.go) \
88
$(wildcard network/*.go) \
99
$(wildcard telemetry/*.go) \
10+
$(wildcard aitelemetry/*.go) \
1011
$(wildcard network/epcommon/*.go) \
1112
$(wildcard network/policy/*.go) \
1213
$(wildcard platform/*.go) \
@@ -313,6 +314,7 @@ test-all:
313314
./netlink/ \
314315
./store/ \
315316
./telemetry/ \
317+
./aitelemetry/ \
316318
./cnm/network/ \
317319
./cni/ipam/ \
318320
./cns/ipamclient/ \

aitelemetry/api.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package aitelemetry
2+
3+
import (
4+
"sync"
5+
6+
"github.com/Azure/azure-container-networking/common"
7+
"github.com/microsoft/ApplicationInsights-Go/appinsights"
8+
)
9+
10+
// Application trace/log structure
11+
type Report struct {
12+
Message string
13+
Context string
14+
CustomDimensions map[string]string
15+
}
16+
17+
// Application metrics structure
18+
type Metric struct {
19+
Name string
20+
Value float64
21+
CustomDimensions map[string]string
22+
}
23+
24+
// TelmetryHandle holds appinsight handles and metadata
25+
type telemetryHandle struct {
26+
telemetryConfig *appinsights.TelemetryConfiguration
27+
appName string
28+
appVersion string
29+
metadata common.Metadata
30+
diagListener appinsights.DiagnosticsMessageListener
31+
client appinsights.TelemetryClient
32+
enableMetadataRefreshThread bool
33+
refreshTimeout int
34+
rwmutex sync.RWMutex
35+
}
36+
37+
// Telemetry Interface to send metrics/Logs to appinsights
38+
type TelemetryHandle interface {
39+
// TrackLog function sends report (trace) to appinsights resource. It overrides few of the existing columns with app information
40+
// and for rest it uses custom dimesion
41+
TrackLog(report Report)
42+
// TrackMetric function sends metric to appinsights resource. It overrides few of the existing columns with app information
43+
// and for rest it uses custom dimesion
44+
TrackMetric(metric Metric)
45+
// Close - should be called for each NewAITelemetry call. Will release resources acquired
46+
Close(timeout int)
47+
}

aitelemetry/metadata_test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"location":"eastus","name":"k8s-agentpool1-42685608-0","offer":"aks","osType":"Linux","placementGroupId":"","platformFaultDomain":"0","platformUpdateDomain":"0","publisher":"microsoft-aks","resourceGroupName":"rgcnideftesttamil","sku":"aks-ubuntu-1604-201902","subscriptionId":"ea821859-912a-4d20-a4dd-e18a3ce5ba2c","tags":"aksEngineVersion:canary;creationSource:aksengine-k8s-agentpool1-42685608-0;orchestrator:Kubernetes:1.10.13;poolName:agentpool1;resourceNameSuffix:42685608","version":"2019.02.12","vmId":"6baf785b-397c-4967-9f75-cdb3d0df66c4","vmSize":"Standard_DS2_v2","KernelVersion":""}

aitelemetry/telemetrywrapper.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package aitelemetry
2+
3+
import (
4+
"runtime"
5+
"time"
6+
7+
"github.com/Azure/azure-container-networking/common"
8+
"github.com/Azure/azure-container-networking/log"
9+
"github.com/Azure/azure-container-networking/store"
10+
"github.com/microsoft/ApplicationInsights-Go/appinsights"
11+
)
12+
13+
const (
14+
resourceGroupStr = "ResourceGroup"
15+
vmSizeStr = "VMSize"
16+
osVersionStr = "OSVersion"
17+
locationStr = "Region"
18+
appVersionStr = "Appversion"
19+
subscriptionIDStr = "SubscriptionID"
20+
defaultTimeout = 10
21+
)
22+
23+
func messageListener() appinsights.DiagnosticsMessageListener {
24+
return appinsights.NewDiagnosticsMessageListener(func(msg string) error {
25+
log.Printf("[AppInsights] [%s] %s\n", time.Now().Format(time.UnixDate), msg)
26+
return nil
27+
})
28+
}
29+
30+
func getMetadata(th *telemetryHandle) {
31+
var metadata common.Metadata
32+
var err error
33+
34+
// check if metadata in memory otherwise initiate wireserver request
35+
for {
36+
metadata, err = common.GetHostMetadata(metadataFile)
37+
if err == nil || !th.enableMetadataRefreshThread {
38+
break
39+
}
40+
41+
log.Printf("[AppInsights] Error getting metadata %v. Sleep for %d", err, th.refreshTimeout)
42+
time.Sleep(time.Duration(th.refreshTimeout) * time.Second)
43+
}
44+
45+
//acquire write lock before writing metadata to telemetry handle
46+
th.rwmutex.Lock()
47+
th.metadata = metadata
48+
th.rwmutex.Unlock()
49+
50+
// Save metadata retrieved from wireserver to a file
51+
kvs, err := store.NewJsonFileStore(metadataFile)
52+
if err != nil {
53+
log.Printf("[AppInsights] Error initializing kvs store: %v", err)
54+
return
55+
}
56+
57+
kvs.Lock(true)
58+
err = common.SaveHostMetadata(th.metadata, metadataFile)
59+
kvs.Unlock(true)
60+
if err != nil {
61+
log.Printf("[AppInsights] saving host metadata failed with :%v", err)
62+
}
63+
}
64+
65+
// NewAITelemetry creates telemetry handle with user specified appinsights key.
66+
func NewAITelemetry(
67+
key string,
68+
appName string,
69+
appVersion string,
70+
batchSize int,
71+
batchInterval int,
72+
enableMetadataRefreshThread bool,
73+
refreshTimeout int,
74+
) TelemetryHandle {
75+
76+
telemetryConfig := appinsights.NewTelemetryConfiguration(key)
77+
telemetryConfig.MaxBatchSize = batchSize
78+
telemetryConfig.MaxBatchInterval = time.Duration(batchInterval) * time.Second
79+
80+
th := &telemetryHandle{
81+
client: appinsights.NewTelemetryClientFromConfig(telemetryConfig),
82+
appName: appName,
83+
appVersion: appVersion,
84+
diagListener: messageListener(),
85+
enableMetadataRefreshThread: enableMetadataRefreshThread,
86+
refreshTimeout: refreshTimeout,
87+
}
88+
89+
if th.enableMetadataRefreshThread {
90+
go getMetadata(th)
91+
} else {
92+
getMetadata(th)
93+
}
94+
95+
return th
96+
}
97+
98+
// TrackLog function sends report (trace) to appinsights resource. It overrides few of the existing columns with app information
99+
// and for rest it uses custom dimesion
100+
func (th *telemetryHandle) TrackLog(report Report) {
101+
// Initialize new trace message
102+
trace := appinsights.NewTraceTelemetry(report.Message, appinsights.Warning)
103+
104+
//Override few of existing columns with metadata
105+
trace.Tags.User().SetAuthUserId(runtime.GOOS)
106+
trace.Tags.Operation().SetId(report.Context)
107+
trace.Tags.Operation().SetParentId(th.appName)
108+
109+
// copy app specified custom dimension
110+
for key, value := range report.CustomDimensions {
111+
trace.Properties[key] = value
112+
}
113+
114+
trace.Properties[appVersionStr] = th.appVersion
115+
116+
// Acquire read lock to read metadata
117+
th.rwmutex.RLock()
118+
metadata := th.metadata
119+
th.rwmutex.RUnlock()
120+
121+
// Check if metadata is populated
122+
if metadata.SubscriptionID != "" {
123+
// copy metadata from wireserver to trace
124+
trace.Tags.User().SetAccountId(th.metadata.SubscriptionID)
125+
trace.Tags.User().SetId(th.metadata.VMName)
126+
trace.Properties[locationStr] = th.metadata.Location
127+
trace.Properties[resourceGroupStr] = th.metadata.ResourceGroupName
128+
trace.Properties[vmSizeStr] = th.metadata.VMSize
129+
trace.Properties[osVersionStr] = th.metadata.OSVersion
130+
}
131+
132+
// send to appinsights resource
133+
th.client.Track(trace)
134+
}
135+
136+
// TrackMetric function sends metric to appinsights resource. It overrides few of the existing columns with app information
137+
// and for rest it uses custom dimesion
138+
func (th *telemetryHandle) TrackMetric(metric Metric) {
139+
// Initialize new metric
140+
aimetric := appinsights.NewMetricTelemetry(metric.Name, metric.Value)
141+
142+
// Acquire read lock to read metadata
143+
th.rwmutex.RLock()
144+
metadata := th.metadata
145+
th.rwmutex.RUnlock()
146+
147+
// Check if metadata is populated
148+
if metadata.SubscriptionID != "" {
149+
aimetric.Properties[locationStr] = th.metadata.Location
150+
aimetric.Properties[subscriptionIDStr] = th.metadata.SubscriptionID
151+
}
152+
153+
// copy custom dimensions
154+
for key, value := range metric.CustomDimensions {
155+
aimetric.Properties[key] = value
156+
}
157+
158+
// send metric to appinsights
159+
th.client.Track(aimetric)
160+
}
161+
162+
// Close - should be called for each NewAITelemetry call. Will release resources acquired
163+
func (th *telemetryHandle) Close(timeout int) {
164+
if timeout <= 0 {
165+
timeout = defaultTimeout
166+
}
167+
168+
// wait for items to be sent otherwise timeout
169+
<-th.client.Channel().Close(time.Duration(timeout) * time.Second)
170+
171+
// Remove diganostic message listener
172+
if th.diagListener != nil {
173+
th.diagListener.Remove()
174+
th.diagListener = nil
175+
}
176+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package aitelemetry
2+
3+
const (
4+
metadataFile = "/tmp/azuremetadata.json"
5+
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package aitelemetry
2+
3+
import (
4+
"os"
5+
"runtime"
6+
"testing"
7+
8+
"github.com/Azure/azure-container-networking/platform"
9+
)
10+
11+
var th TelemetryHandle
12+
13+
func TestMain(m *testing.M) {
14+
15+
if runtime.GOOS == "linux" {
16+
platform.ExecuteCommand("cp metadata_test.json /tmp/azuremetadata.json")
17+
} else {
18+
platform.ExecuteCommand("copy metadata_test.json azuremetadata.json")
19+
}
20+
21+
exitCode := m.Run()
22+
23+
if runtime.GOOS == "linux" {
24+
platform.ExecuteCommand("rm /tmp/azuremetadata.json")
25+
} else {
26+
platform.ExecuteCommand("del azuremetadata.json")
27+
}
28+
29+
os.Exit(exitCode)
30+
}
31+
32+
func TestNewAITelemetry(t *testing.T) {
33+
th = NewAITelemetry("00ca2a73-c8d6-4929-a0c2-cf84545ec225", "testapp", "v1.0.26", 4096, 2, false, 10)
34+
if th == nil {
35+
t.Errorf("Error intializing AI telemetry")
36+
}
37+
}
38+
39+
func TestTrackMetric(t *testing.T) {
40+
metric := Metric{
41+
Name: "test",
42+
Value: 1.0,
43+
CustomDimensions: make(map[string]string),
44+
}
45+
46+
metric.CustomDimensions["dim1"] = "col1"
47+
th.TrackMetric(metric)
48+
}
49+
50+
func TestTrackLog(t *testing.T) {
51+
report := Report{
52+
Message: "test",
53+
Context: "10a",
54+
CustomDimensions: make(map[string]string),
55+
}
56+
57+
report.CustomDimensions["dim1"] = "col1"
58+
th.TrackLog(report)
59+
}
60+
61+
func TestClose(t *testing.T) {
62+
th.Close(10)
63+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package aitelemetry
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
)
7+
8+
var (
9+
metadataFile = filepath.FromSlash(os.Getenv("TEMP")) + "\\azuremetadata.json"
10+
)

0 commit comments

Comments
 (0)