├── .gitignore
├── License.txt
├── README.md
├── ThirdPartyNotices.txt
├── build.gradle
├── docker
├── Dockerfile
└── requirements.txt
├── gradle
├── remote.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── python
├── .gitignore
├── appinsights
│ ├── __init__.py
│ ├── dockercollector.py
│ ├── dockerconvertors.py
│ ├── dockerinjector.py
│ ├── dockerwrapper.py
│ └── program.py
├── bootstrap.py
└── tests
│ ├── __init__.py
│ └── appinsights_tests
│ ├── TestDockerCollector.py
│ ├── TestDockerConvertors.py
│ ├── TestDockerInjector.py
│ ├── TestDockerWrapper.py
│ └── __init__.py
├── sdk
└── ApplicationInsights.xml
├── settings.gradle
└── src
├── main
└── java
│ └── com
│ └── microsoft
│ └── applicationinsights
│ ├── AgentBootstrapper.java
│ ├── agent
│ ├── DockerAgent.java
│ └── DockerContainerContextAgent.java
│ ├── common
│ ├── ApplicationInsightsSender.java
│ ├── ArrayUtils.java
│ ├── Constants.java
│ ├── StringUtils.java
│ └── TelemetryFactory.java
│ ├── contracts
│ ├── ContainerStateEvent.java
│ └── ContainerStatsMetric.java
│ ├── providers
│ ├── EventProvider.java
│ ├── MetricProvider.java
│ └── StateProvider.java
│ └── python
│ ├── ContainerContextPythonBoostrapper.java
│ ├── ContainerStatePythonBootstrapper.java
│ ├── MetricCollectionPythonBoostrapper.java
│ ├── ProcessBuilder.java
│ ├── PythonBootstrapper.java
│ └── PythonProcessBuilder.java
└── test
├── java
└── com
│ └── microsoft
│ └── applicationinsights
│ ├── AgentBootstrapperTests.java
│ ├── agent
│ └── DockerAgentTests.java
│ ├── common
│ ├── ApplicationInsightsSenderTests.java
│ ├── ArrayUtilsTests.java
│ ├── StringUtilsTests.java
│ ├── TelemetryFactoryTests.java
│ └── TestConstants.java
│ ├── contracts
│ ├── ContainerStateEventTests.java
│ └── ContainerStatsMetricTests.java
│ ├── providers
│ ├── MetricProviderTests.java
│ └── StateProviderTests.java
│ └── python
│ └── MetricProviderPythonBootstrapperTests.java
└── resources
├── test_metric_event.py
└── test_state_event.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python exclusions
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | #Visual Studio files
7 | *.o
8 | *.d
9 | *.so
10 | *.class
11 | *.sdf
12 | *.opensdf
13 | *.suo
14 | *.user
15 | Debug/
16 | Release/
17 | ipch/
18 |
19 | # Eclipse
20 | .classpath
21 | .project
22 | .settings/
23 |
24 | # Gradle
25 | .gradle
26 | build/
27 |
28 | # Ignore Gradle GUI config
29 | gradle-app.setting
30 |
31 | ## Directory-based project format:
32 | .idea
33 | # if you remove the above rule, at least ignore the following:
34 |
35 |
36 | # User-specific stuff:
37 | # .idea/workspace.xml
38 | # .idea/tasks.xml
39 | # .idea/dictionaries
40 |
41 |
42 | # Sensitive or high-churn files:
43 | # .idea/dataSources.ids
44 | # .idea/dataSources.xml
45 | # .idea/sqlDataSources.xml
46 | # .idea/dynamic.xml
47 | # .idea/uiDesigner.xml
48 |
49 |
50 | # Gradle:
51 | # .idea/gradle.xml
52 | # .idea/libraries
53 |
54 |
55 | # Mongo Explorer plugin:
56 | # .idea/mongoSettings.xml
57 |
58 |
59 | ## File-based project format:
60 | *.ipr
61 | *.iws
62 |
63 |
64 | ## Plugin-specific files:
65 |
66 | # IntelliJ
67 | out/
68 | *.iml
69 |
70 | # mpeltonen/sbt-idea plugin
71 | .idea_modules/
72 |
73 |
74 | # JIRA plugin
75 | atlassian-ide-plugin.xml
76 |
77 |
78 | # Crashlytics plugin (for Android Studio and IntelliJ)
79 | com_crashlytics_export_strings.xml
80 |
81 | # Mac
82 | .DS_Store
83 |
84 | # Click-Once directory
85 | publish/
86 |
87 | # Windows image file caches
88 | Thumbs.db
89 | ehthumbs.db
90 |
91 | # Folder config file
92 | Desktop.ini
93 |
94 | # Recycle Bin used on file shares
95 | $RECYCLE.BIN/
96 | /Backend/OrderService/.gradle/1.11/taskArtifacts
97 | /Backend/OrderService/build
98 | /Backend/OrderService/.idea
99 | Backend/OrderService/OrderService.iml
100 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | ApplicationInsights-Docker
2 | Copyright (c) Microsoft Corporation
3 | All rights reserved.
4 |
5 | MIT License
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this
7 | software and associated documentation files (the ""Software""), to deal in the Software
8 | without restriction, including without limitation the rights to use, copy, modify, merge,
9 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit
10 | persons to whom the Software is furnished to do so, subject to the following conditions:
11 | The above copyright notice and this permission notice shall be included in all copies or
12 | substantial portions of the Software.
13 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18 | DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repo is [not currently maintained or supported](https://docs.microsoft.com/en-us/azure/azure-monitor/app/docker) by Microsoft. Check out our official documentation for the latest investments in [container monitoring](https://docs.microsoft.com/en-us/azure/azure-monitor/insights/container-insights-overview).
2 |
3 | Application Insights for Docker
4 | ===============================
5 |
6 | Visual Studio [Application Insights][appinsights-overview] for Docker helps you monitor your containerized applications by collecting telemetry about the performance and activity of your Docker host, Docker containers and the applications running within them.
7 | The Application Insights container talks to the Docker agent and sends telemetry data back to [Application Insights][appinsights-home], providing you with diagnostics and data analysis tools.
8 |
9 | ## What is this repo?
10 |
11 | This repo contains the source code for Application Insights for Docker image.
12 | For more information, see [Application Insights for Docker image homepage][appinsights-docker-image] in Docker Hub.
13 |
14 |
15 | ## Microsoft Open Source Code of Conduct
16 |
17 |
18 |
19 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
20 |
21 | [appinsights-home]: https://azure.microsoft.com/en-us/services/application-insights/
22 | [appinsights-overview]: https://azure.microsoft.com/en-us/documentation/articles/app-insights-overview/
23 | [appinsights-docker-image]: https://hub.docker.com/r/microsoft/applicationinsights/
24 |
--------------------------------------------------------------------------------
/ThirdPartyNotices.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/ApplicationInsights-Docker/95b86bfdfadcd9aa9b243447b32dce0a053bb0d0/ThirdPartyNotices.txt
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.github.johnrengelman.shadow' version '1.2.1'
3 | }
4 |
5 | apply plugin: 'java'
6 | apply from: "$rootDir/gradle/remote.gradle"
7 |
8 | version = '0.9'
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
14 | dependencies {
15 | compile group: 'com.microsoft.azure', name: 'applicationinsights-core', version: '1.0.3'
16 | compile group: 'com.google.code.gson', name: 'gson', version: '1.7.2'
17 |
18 | testCompile 'junit:junit:4.11'
19 | testCompile group: 'org.mockito', name: 'mockito-all', version: '1.8.0'
20 | }
21 |
22 | // region Prepare Docker build folder
23 |
24 | project.ext.localDockerDir = "$rootDir/build/docker"
25 | task copyPythonScripts(type: Copy) {
26 | from "$rootDir/python"
27 | into "$localDockerDir/python"
28 | exclude "**/__pycache__", "**/.idea"
29 | }
30 |
31 | task copyDockerFiles(type: Copy) {
32 | from "$rootDir/docker"
33 | into localDockerDir
34 | }
35 |
36 | task copyApplicationLibs(type: Copy) {
37 | from project.buildDir.absolutePath + "/libs"
38 |
39 | // TODO: remove when the Java SDK bug will be fixed (config file parsing failed when provided as resource)
40 | from "$rootDir/sdk"
41 |
42 | into localDockerDir
43 | }
44 |
45 | // Installing python required libraries.
46 | task installPythonLibraties << {
47 | File theInfoFile = new File("$rootDir/docker/requirements.txt")
48 | theInfoFile.eachLine { line ->
49 | exec {
50 | executable "python"
51 | args "-m", "pip", "install", line
52 | }
53 | }
54 | }
55 |
56 | task runPythonTests << {
57 | exec {
58 | executable "python"
59 | args "-m", "unittest", "discover", "-s", "$rootDir/python"
60 | }
61 | }
62 |
63 | tasks.test.dependsOn installPythonLibraties
64 | tasks.runPythonTests.dependsOn installPythonLibraties
65 | tasks.test.finalizedBy runPythonTests
66 |
67 | // Creating a fat-jar instead of the original one, to be added to the image more easily.
68 |
69 | // TODO: create a specific task to prepare docker folder.
70 | jar.enabled = false
71 | tasks.shadowJar.finalizedBy copyPythonScripts, copyDockerFiles, copyApplicationLibs
72 |
73 | shadowJar {
74 | classifier = ''
75 | }
76 |
77 | // endregion Prepare Docker build folder
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM java:8u66
2 |
3 | RUN apt-get -y -qq update
4 | RUN apt-get -y -qq remove python
5 | RUN apt-get -y -qq autoremove
6 | RUN apt-get -y -qq install python3.4
7 | RUN ln -s /usr/bin/python3.4 /usr/bin/python
8 |
9 | # TODO: run a script to install all libraries from requirements.txt
10 | RUN apt-get -y -qq install python3-pip
11 | RUN python -m pip install python-dateutil==2.4.2
12 |
13 | # docker-py is dependent on the 'requests' module which currently has a bug. Therefore, the docker-py
14 | # must be installed last otherwise no other modules can be installed.
15 | RUN python -m pip install docker-py==1.3.1
16 |
17 | COPY . /usr/appinsights/docker
18 | WORKDIR /usr/appinsights/docker
19 |
20 | # TODO: library version as parameter.
21 | ENTRYPOINT ["java","-cp", "/usr/appinsights/docker/ApplicationInsights-Docker-0.9.jar", "com.microsoft.applicationinsights.AgentBootstrapper"]
--------------------------------------------------------------------------------
/docker/requirements.txt:
--------------------------------------------------------------------------------
1 | python-dateutil
2 | docker-py
3 |
--------------------------------------------------------------------------------
/gradle/remote.gradle:
--------------------------------------------------------------------------------
1 | task remoteDeployment {
2 | dependsOn clean
3 |
4 | def remoteSettings = System.getenv("DOCKER_REMOTE_SETTINGS_FILE_PATH")
5 |
6 | if (remoteSettings) {
7 | Properties props = new Properties()
8 | props.load(new FileInputStream(remoteSettings))
9 |
10 | project.ext.machineName = props.get("machineName")
11 | project.ext.userName = props.get("userName")
12 | project.ext.pass = props.get("pass")
13 | project.ext.ikey = props.get("ikey")
14 | } else {
15 | ext.requriedProperties = ["machineName", "userName", "pass"]
16 | }
17 |
18 | group = 'verification'
19 | description = 'Builds and starts Application Insights container on remote machine.'
20 |
21 | // TODO: check plink, pscp installations first.
22 |
23 | // TODO: Change after creating specific task for creating the docker folder.
24 | dependsOn shadowJar
25 |
26 | def imageName = "ai-develop-remote";
27 | def containerName = imageName
28 | def remoteDockerDir = "/remote/docker"
29 |
30 | doLast {
31 | logger.info("Deploying image to $userName@$machineName.")
32 |
33 | try
34 | {
35 | runRemoteCommand("Cleaning exiting remote Docker resources.", "rm -rf $remoteDockerDir")
36 | runRemoteCommand("Creating remote Docker folder.", "mkdir $remoteDockerDir")
37 | runRemoteCopy("Copying local Docker folder to remote machine.", "$localDockerDir/*", "$userName@$machineName:$remoteDockerDir")
38 | runRemoteCommand("Building Docker image on remote machine.", "cd $remoteDockerDir; docker build -t $imageName .")
39 | runRemoteCommand("Killing existing container, if exists.", "docker kill $containerName && docker rm $containerName || true")
40 | runRemoteCommand("Starting Docker image.", "docker run -v /var/run/docker.sock:/docker.sock --name $containerName -d $imageName ikey=$ikey")
41 | }
42 | finally
43 | {
44 | // TODO: anything to finalize?
45 | }
46 | }
47 | }
48 |
49 | def runRemoteCopy(description, from, into) {
50 | logger.info("[runRemoteCopy] $description")
51 | exec {
52 | executable "pscp.exe"
53 | args "-r", "-pw", pass, "\"$from\"", "\"$into\""
54 | }
55 | }
56 |
57 | def runRemoteCommand(description, command) {
58 | logger.info("[runRemoteCommand] $description")
59 | exec {
60 | executable "plink.exe"
61 | args "-pw", pass, "$userName@$machineName", "\"$command\""
62 | }
63 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/ApplicationInsights-Docker/95b86bfdfadcd9aa9b243447b32dce0a053bb0d0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jul 22 11:14:28 IDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/python/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
--------------------------------------------------------------------------------
/python/appinsights/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | # __author__ = 'galha'
23 | from . import program
24 | from . import dockercollector
25 |
--------------------------------------------------------------------------------
/python/appinsights/dockercollector.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | __author__ = 'galha'
23 |
24 | import concurrent.futures
25 | import time
26 | import dateutil.parser
27 | from appinsights.dockerwrapper import DockerWrapperError
28 | from appinsights import dockerconvertors
29 |
30 |
31 | class DockerCollector(object):
32 | """ The application insights docker collector,
33 | used to collect data from the docker remote API (events, and performance counters)
34 | """
35 |
36 | _cmd_template = "/bin/sh -c \"[ -f {file} ] && cat {file}\""
37 |
38 | def _default_print(text):
39 | print(text, flush=True)
40 |
41 | def __init__(self, docker_wrapper, docker_injector, samples_in_each_metric=2, send_event=_default_print,
42 | sdk_file='/usr/appinsights/docker/sdk.info'):
43 | """ Initializes a new instance of the class.
44 |
45 | :param docker_wrapper: A docker client wrapper instance
46 | :param docker_injector: A docker docker injector instance
47 | :param samples_in_each_metric: The Number of samples to use in each metric
48 | :param send_event: Function to send event
49 | :param sdk_file: The sdk file location
50 | :return:
51 | """
52 | super().__init__()
53 | assert docker_wrapper is not None, 'docker_client cannot be None'
54 | assert docker_injector is not None, 'docker_injector cannot be None'
55 | assert samples_in_each_metric > 1, 'samples_in_each_metric must be greater than 1, given: {0}'.format(
56 | samples_in_each_metric)
57 | self._sdk_file = sdk_file
58 | self._docker_wrapper = docker_wrapper
59 | self._docker_injector = docker_injector
60 | self._samples_in_each_metric = samples_in_each_metric
61 | self._send_event = send_event
62 | self._my_container_id = None
63 | self._containers_state = {}
64 |
65 | def collect_stats_and_send(self):
66 | """
67 | Collects docker metrics from docker and sends them to sender
68 | cpu, memory, rx_bytes ,tx_bytes, blkio metrics
69 | """
70 |
71 | if self._my_container_id is None:
72 | self._my_container_id = self._docker_injector.get_my_container_id()
73 |
74 | host_name = self._docker_wrapper.get_host_name()
75 | containers = self._docker_wrapper.get_containers()
76 | self._update_containers_state(containers=containers)
77 | containers_without_sdk = [v['container'] for k, v in self._containers_state.items() if
78 | k == self._my_container_id or v['ikey'] is None]
79 |
80 | with concurrent.futures.ThreadPoolExecutor(max_workers=max(len(containers), 30)) as executor:
81 | container_stats = list(
82 | executor.map(
83 | lambda container: (container, self._docker_wrapper.get_stats(container=container,
84 | stats_to_bring=self._samples_in_each_metric)),
85 | containers_without_sdk))
86 |
87 | for container, stats in [(container, stats) for container, stats in container_stats if len(stats) > 1]:
88 | metrics = dockerconvertors.convert_to_metrics(stats)
89 | properties = dockerconvertors.get_container_properties(container, host_name)
90 | for metric in metrics:
91 | self._send_event({'metric': metric, 'properties': properties})
92 |
93 | def collect_container_events(self):
94 | """ Collects the container events (start, stop, die, pause, unpause)
95 | and sends then using the send_event function given in the constructor
96 | :return:
97 | """
98 | event_name_template = 'docker-container-{0}'
99 | host_name = self._docker_wrapper.get_host_name()
100 | for event in self._docker_wrapper.get_events():
101 | status = event['status']
102 | if status not in ['start', 'stop', 'die', 'restart', 'pause', 'unpause']:
103 | continue
104 |
105 | event_name = event_name_template.format(status)
106 | inspect = self._docker_wrapper.get_inspection(event)
107 | properties = dockerconvertors.get_container_properties_from_inspect(inspect, host_name)
108 |
109 | ikey_to_send_event = self._get_container_sdk_ikey_from_containers_state(properties['Docker container id'])
110 |
111 | properties['docker-status'] = status
112 | properties['docker-Created'] = inspect['Created']
113 | properties['docker-StartedAt'] = inspect['State']['StartedAt']
114 | properties['docker-RestartCount'] = inspect['RestartCount']
115 |
116 | if status in ['stop', 'die']:
117 | properties['docker-FinishedAt'] = inspect['State']['FinishedAt']
118 | properties['docker-ExitCode'] = inspect['State']['ExitCode']
119 |
120 | error = inspect['State']['Error']
121 | properties['docker-Error'] = error if (error is not None) else ""
122 | duration = dateutil.parser.parse(properties['docker-FinishedAt']) - dateutil.parser.parse(
123 | properties['docker-StartedAt'])
124 | duration_seconds = duration.total_seconds()
125 | properties['docker-duration-seconds'] = duration_seconds
126 | properties['docker-duration-minutes'] = duration_seconds / 60
127 | properties['docker-duration-hours'] = duration_seconds / 3600
128 | properties['docker-duration-days'] = duration_seconds / 86400
129 | event_data = {'name': event_name, 'ikey': ikey_to_send_event if ikey_to_send_event is not None else '', 'properties': properties}
130 | self._send_event(event_data)
131 |
132 | @staticmethod
133 | def remove_old_containers(current_containers, new_containers):
134 | """
135 | This function removes all old containers that have been stopped.
136 |
137 | :param current_containers: The containers currently in cache.
138 | :param new_containers: The latest containers collection.
139 | :rtype : dict
140 | """
141 | curr_containers_ids = {c['Id']: c for c in new_containers}
142 | keys = [k for k in current_containers]
143 | for key in [key for key in keys if key not in curr_containers_ids]:
144 | if current_containers[key]['unregistered'] is None:
145 | current_containers[key]['unregistered'] = time.time()
146 | else:
147 | if current_containers[key]['unregistered'] < time.time() - 60:
148 | del current_containers[key]
149 |
150 | return current_containers
151 |
152 | def _get_container_sdk_info(self, container):
153 | try:
154 | result = self._docker_wrapper.run_command(container,
155 | DockerCollector._cmd_template.format(file=self._sdk_file))
156 | result = result.strip()
157 |
158 | return result if result != '' else None
159 | except DockerWrapperError:
160 | return None
161 |
162 | def _get_container_sdk_ikey_from_containers_state(self, container_id):
163 | if container_id not in self._containers_state.keys():
164 | containers = self._docker_wrapper.get_containers()
165 | self._update_containers_state(containers=containers)
166 |
167 | if container_id in self._containers_state.keys():
168 | return self._containers_state[container_id]['ikey']
169 | else:
170 | return None
171 |
172 | def _update_containers_state(self, containers):
173 | self._containers_state = DockerCollector.remove_old_containers(self._containers_state, containers)
174 | with concurrent.futures.ThreadPoolExecutor(max_workers=max(len(containers), 30)) as executor:
175 | list(executor.map(lambda c: self._update_container_state(c), containers))
176 |
177 | def _update_container_state(self, container):
178 | id = container['Id']
179 | if id not in self._containers_state:
180 | for i in range(5):
181 | ikey = self._get_container_sdk_ikey(container)
182 | self._containers_state[id] = {'ikey': ikey, 'registered': time.time(), 'unregistered': None, 'container': container}
183 |
184 | if ikey is not None:
185 | return ikey
186 |
187 | time.sleep(1)
188 |
189 | return None
190 |
191 | status = self._containers_state[id]
192 | if status['ikey'] is not None:
193 | return status['ikey']
194 |
195 | if status['registered'] > time.time() - 60:
196 | ikey = self._get_container_sdk_ikey(container)
197 | status['ikey'] = ikey
198 | return ikey
199 |
200 | return None
201 |
202 | def _get_container_sdk_ikey(self, container):
203 | sdk_info_file_content = self._get_container_sdk_info(container)
204 | if sdk_info_file_content is None:
205 | return None
206 | splits = sdk_info_file_content.split('=')
207 | if len(splits) < 2:
208 | return None
209 | return sdk_info_file_content.split('=')[1]
--------------------------------------------------------------------------------
/python/appinsights/dockerconvertors.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | __author__ = 'galha'
23 | import statistics
24 |
25 | def convert_to_metrics(stats):
26 | """ convert the docker container stats list to ai metrices
27 | :param stats: The docker container stats list
28 | :return: List of Ai Metrics (cpu, rx, tx, blkio),
29 | each metric is a dict of (name, value, count, min, max, and std)
30 | """
31 | assert stats is not None and len(stats)>1 ,"stats should have at least 2 samples in it"
32 | return [get_cpu_metric(stats=stats),
33 | get_simple_metric(
34 | metric_name='Available Bytes',
35 | func=lambda stat: stat['memory_stats']['limit'] - stat['memory_stats']['usage'],
36 | stats=stats
37 | ),
38 | get_per_second_metric(
39 | metric_name='Docker RX Bytes',
40 | func=lambda stat: stat['network']['rx_bytes'],
41 | stats=stats
42 | ),
43 | get_per_second_metric(
44 | metric_name='Docker TX Bytes',
45 | func=lambda stat: stat['network']['tx_bytes'],
46 | stats=stats
47 | ),
48 | get_per_second_metric(
49 | metric_name='Docker Blkio Bytes',
50 | func= get_total_blkio,
51 | stats=stats
52 | )]
53 |
54 | def get_total_blkio(stat):
55 | """ Gets the total blkio out of the docker stat
56 | :param stat: The docker stat
57 | :return: The blkio
58 | """
59 | io_list = stat['blkio_stats']['io_service_bytes_recursive']
60 | if len(io_list)>0:
61 | total_dics = list(filter(lambda dic: dic['op'] == 'Total', io_list))
62 | if len(total_dics)>0:
63 | return total_dics[0]['value']
64 | else:
65 | return 0
66 |
67 | def get_cpu_metric(stats):
68 | """ Gets the cpu metric from the docker stats list
69 | :param stats: The docker stats list
70 | :return: A cpu metric
71 | """
72 | assert stats is not None and len(stats)>1 ,\
73 | "the 'stats' samples must contain more than 1 statistics in order to calclulate the cpu metric"
74 |
75 | cpu_list = [stat['cpu_stats']['cpu_usage']['total_usage'] for time, stat in stats]
76 | system_cpu_list = [stat['cpu_stats']['system_cpu_usage'] for time, stat in stats]
77 | cpu2 = cpu_list[1:]
78 | cpu1 = cpu_list[:len(cpu_list) - 1]
79 | system2 = system_cpu_list[1:]
80 | system1 = system_cpu_list[: len(system_cpu_list) - 1]
81 | cpu_percents = [100.0 * (cpu_curr - cpu_prev) / (system_curr - system_prev) for
82 | cpu_curr, cpu_prev, system_curr, system_prev in list(zip(cpu2, cpu1, system2, system1))]
83 |
84 | return {'name':'% Processor Time',
85 | 'value':statistics.mean(cpu_percents),
86 | 'count':len(cpu_percents),
87 | 'min':min(cpu_percents),
88 | 'max':max(cpu_percents),
89 | 'std':statistics.stdev(cpu_percents) if len(cpu_percents) > 1 else None}
90 |
91 | def get_per_second_metric(metric_name, func, stats):
92 | """ Gets a per second metric out of the docker stats list,
93 | per second valuates the time difference in time between every two samples
94 | :param metric_name: The metric name
95 | :param func: A function which gets the value of the sample out of the stat object
96 | :param stats: The docker stats list
97 | :return: Ai metric
98 | """
99 | assert metric_name is not None, "metric_name shoud not be None"
100 | assert func is not None, "func should not be None"
101 | assert stats is not None and len(stats)>1, "stats should have more than 1 samples in it"
102 | stats2 = stats[1:]
103 | stats1 = stats[:len(stats) - 1]
104 | samples = [(func(s2)-func(s1)) / (time2 - time1) for (time2, s2), (time1, s1) in list(zip(stats2, stats1))]
105 | return {'name':metric_name,
106 | 'value':statistics.mean(samples),
107 | 'count':len(samples),
108 | 'min':min(samples),
109 | 'max':max(samples),
110 | 'std':statistics.stdev(samples) if len(samples) > 1 else None}
111 |
112 | def get_simple_metric(metric_name, func, stats):
113 | """ Gets an ai metric from the stats list (count, average, min, max and std)
114 | :param metric_name: The metric name
115 | :param func: A function which gets the value of the sample out of the stat object
116 | :param stats: The docker stats list
117 | :return: Ai metric
118 | """
119 | assert metric_name is not None
120 | assert func is not None
121 | assert stats is not None and len(stats)>1
122 | samples = [func(stat) for time, stat in stats]
123 | return {'name': metric_name,
124 | 'value':statistics.mean(samples),
125 | 'count':len(samples),
126 | 'min':min(samples),
127 | 'max':max(samples),
128 | 'std':statistics.stdev(samples) if len(samples) > 1 else None}
129 |
130 | def get_container_properties(container, host_name):
131 | """ Gets the container properties from a container object
132 | :param container: The container object
133 | :param host_name: The host name
134 | :return: dict of (Docker host, Docker image, Docker container id, Docker container name)
135 | """
136 | return {'Docker host': host_name,
137 | 'Docker image': container.get('Image', 'N/A'),
138 | 'Docker container id': container.get('Id', 'N/A'),
139 | 'Docker container name': container.get('Names', ['N/A'])[0]}
140 |
141 |
142 | def get_container_properties_from_inspect(inspect, host_name):
143 | """ Gets the container properties from an inspect object
144 | :param inspect: The inspect object
145 | :param host_name: The host name
146 | :return: dict of (Docker host, Docker image, Docker container id, Docker container name)
147 | """
148 | return {'Docker host': host_name,
149 | 'Docker image': inspect['Config'].get('Image', 'N/A') if 'Config' in inspect else 'N/A',
150 | 'Docker container id': inspect.get('Id', 'N/A'),
151 | 'Docker container name': inspect.get('Names', [inspect.get('Name', 'N/A')])[0]}
152 |
153 |
--------------------------------------------------------------------------------
/python/appinsights/dockerinjector.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | import sys
23 | from appinsights.dockerwrapper import DockerWrapperError
24 |
25 | __author__ = 'galha'
26 |
27 | import concurrent.futures, os, re
28 | from appinsights import dockerconvertors
29 |
30 |
31 | class DockerInjector(object):
32 | """
33 | Application insights docker container injector,
34 | used to inject the container context to running containers
35 | """
36 | _default_bash = "bash"
37 | _mkdir_template = "mkdir -p \"{directory}\""
38 | _create_file_template = "/bin/sh -c \"[ ! -f {directory}/{file} ] && `printf '{properties}' > {directory}/{file}` && echo created file || echo file already exists\""
39 |
40 | def __init__(self, docker_wrapper, docker_info_path):
41 | """ Initializes a new instance of the class.
42 | :param docker_wrapper: a docker wrapper instance
43 | :param docker_info_path: (str). The file path where to inject the context
44 | :return:
45 | """
46 | self._docker_wrapper = docker_wrapper
47 | self._docker_info_path = docker_info_path
48 | self._host_name = None
49 | self._dirName = os.path.dirname(docker_info_path)
50 | self._fileName = os.path.basename(docker_info_path)
51 | self._my_container_id = None
52 |
53 | def inject_context(self):
54 | """ Injects the context to all running containers
55 | :return:
56 | """
57 | containers = self._docker_wrapper.get_containers()
58 | if self._host_name is None:
59 | self._host_name = self._docker_wrapper.get_host_name()
60 |
61 | with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
62 | results = list(
63 | executor.map(lambda container: (container["Id"], self.inject_container(container)),containers))
64 |
65 | return results
66 |
67 | def start(self):
68 | """ start to inject the context to all running containers,
69 | and listen to containers events to inject to inject the context to new created containers.
70 | :return:
71 | """
72 | with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
73 | executor.submit(lambda : self.inject_context())
74 | executor.map(
75 | lambda event: self.inject_container(event),
76 | filter(
77 | lambda event: event['status'] in ['start', 'restart', 'unpause'],
78 | self._docker_wrapper.get_events()))
79 |
80 | @property
81 | def docker_info_path(self):
82 | """ Gets the docker info file path
83 | :return: The docker info file path
84 | """
85 | return self._docker_info_path
86 |
87 | def get_my_container_id(self):
88 | """ Gets the container id of the caller.
89 | :return: The container Id of the caller, None if the call was made not within a container
90 | """
91 | if self._my_container_id is not None:
92 | return self._my_container_id
93 |
94 | if not os.path.exists(self.docker_info_path):
95 | self.inject_context();
96 |
97 | # we are not running in a container
98 | if not os.path.exists(self.docker_info_path):
99 | return None
100 |
101 | # get the context from the injected file
102 | with open(self.docker_info_path, mode='r') as f:
103 | context = f.read()
104 | match = re.search('Docker container id=([a-zA-Z0-9]+)', context)
105 | if match:
106 | self._my_container_id = match.group(1)
107 | return self._my_container_id
108 |
109 | # this happens only when we run the code not within a container
110 | return None
111 |
112 | def inject_container(self, container):
113 | """ Injects the container context into the given container
114 | :param container: The container to inject
115 | :return:
116 | """
117 | try:
118 | mkdir_cmd = DockerInjector._mkdir_template.format(directory=self._dirName)
119 | self._docker_wrapper.run_command(container=container, cmd=mkdir_cmd)
120 | properties = self._get_properties(container)
121 | properties_string = ",".join(["{key}={value}".format(key=k, value=v) for k, v in properties.items()])
122 | docker_info_cmd = DockerInjector._create_file_template.format(
123 | directory=self._dirName,
124 | file=self._fileName,
125 | properties=properties_string)
126 |
127 | result = self._docker_wrapper.run_command(container=container, cmd=docker_info_cmd)
128 | return result
129 | except DockerWrapperError as e:
130 | return e
131 |
132 | def _get_properties(self, item):
133 | if 'status' in item:
134 | inspect = self._docker_wrapper.get_inspection(item)
135 | return dockerconvertors.get_container_properties_from_inspect(inspect=inspect, host_name=self._host_name)
136 | return dockerconvertors.get_container_properties(container=item, host_name=self._host_name)
--------------------------------------------------------------------------------
/python/appinsights/dockerwrapper.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | __author__ = 'galha'
23 |
24 | import requests
25 | from requests.packages.urllib3.exceptions import ReadTimeoutError, HTTPError
26 | from itertools import islice
27 | from docker import errors
28 | from docker import Client
29 | import time
30 |
31 | def get_production_docker_wrapper(base_url):
32 | return ProductionWrapper(base_url=base_url)
33 |
34 |
35 | class DockerClientWrapper(object):
36 | """ A wrapper class on the docker client
37 | """
38 |
39 | def __init__(self, docker_client):
40 | """ Initializes a new instance of the class.
41 | :param docker_client:
42 | :return:
43 | """
44 | assert docker_client is not None, 'docker_client cannot be None'
45 | self._client = docker_client
46 |
47 | def get_host_name(self):
48 | return self._client.info().get('Name', 'N/A')
49 |
50 | def get_containers(self):
51 | return self._client.containers()
52 |
53 | def get_stats(self, container, stats_to_bring):
54 | stats = []
55 | try:
56 | for stat in islice(self._client.stats(container=container, decode=True), 0, stats_to_bring, 1):
57 | stats.append((time.time(), stat))
58 | except (errors.APIError, ReadTimeoutError, requests.exceptions.ReadTimeout, HTTPError):
59 | pass
60 | return stats
61 |
62 | def run_command(self, container, cmd):
63 | try:
64 | exec_id = self._client.exec_create(container, cmd)
65 | output = self._client.exec_start(exec_id=exec_id)
66 | return output.decode('utf-8')
67 | except (errors.APIError, ReadTimeoutError, requests.exceptions.ReadTimeout, HTTPError) as e:
68 | raise DockerWrapperError(e)
69 |
70 | def get_events(self):
71 | for event in self._client.events(decode=True):
72 | if 'id' in event:
73 | event['Id'] = event['id']
74 | yield event
75 |
76 | def get_inspection(self, container):
77 | try:
78 | return self._client.inspect_container(container=container)
79 | except (errors.APIError, ReadTimeoutError, requests.exceptions.ReadTimeout, HTTPError) as e:
80 | raise DockerWrapperError(e)
81 |
82 |
83 | class ProductionWrapper(object):
84 | def __init__(self, base_url):
85 | self._fast_operations_client = DockerClientWrapper(Client(base_url=base_url, timeout=10))
86 | self._slow_operations_client = DockerClientWrapper(Client(base_url=base_url, timeout=60))
87 |
88 | def get_host_name(self):
89 | return self._fast_operations_client.get_host_name()
90 |
91 | def get_containers(self):
92 | return self._fast_operations_client.get_containers()
93 |
94 | def get_stats(self, container, stats_to_bring):
95 | return self._fast_operations_client.get_stats(container=container, stats_to_bring=stats_to_bring)
96 |
97 | def run_command(self, container, cmd):
98 | return self._slow_operations_client.run_command(container=container, cmd=cmd)
99 |
100 | def get_events(self):
101 | return self._fast_operations_client.get_events()
102 |
103 | def get_inspection(self, container):
104 | return self._slow_operations_client.get_inspection(container=container)
105 |
106 |
107 | class DockerWrapperError(Exception):
108 | pass
109 |
--------------------------------------------------------------------------------
/python/appinsights/program.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | __author__ = 'galha'
23 |
24 | from appinsights.dockercollector import DockerCollector
25 | from appinsights.dockerwrapper import get_production_docker_wrapper
26 | from appinsights.dockerinjector import DockerInjector
27 | import time
28 | import sys
29 |
30 | def run_injector(docker_socket, docker_info_path):
31 | injector = DockerInjector(
32 | get_production_docker_wrapper(base_url=docker_socket),
33 | docker_info_path=docker_info_path)
34 |
35 | while True:
36 | injector.start()
37 | time.sleep(30)
38 |
39 | def run_collect_performance_counters(docker_socket, sdk_file, docker_info_file, collect_interval):
40 | docker_wrapper=get_production_docker_wrapper(base_url=docker_socket)
41 | docker_injector = DockerInjector(docker_wrapper=docker_wrapper, docker_info_path=docker_info_file)
42 | collector = DockerCollector(
43 | docker_wrapper=docker_wrapper,
44 | docker_injector=docker_injector,
45 | samples_in_each_metric=5,
46 | sdk_file=sdk_file)
47 |
48 | while True:
49 | collector.collect_stats_and_send()
50 | time.sleep(float(collect_interval))
51 |
52 | def run_collect_containers_events(docker_socket, docker_info_file, sdk_file):
53 | docker_wrapper=get_production_docker_wrapper(base_url=docker_socket)
54 | docker_injector = DockerInjector(docker_wrapper=docker_wrapper, docker_info_path=docker_info_file)
55 | collector = DockerCollector(
56 | docker_wrapper=docker_wrapper,
57 | docker_injector=docker_injector,
58 | samples_in_each_metric=5,
59 | sdk_file=sdk_file)
60 | while True:
61 | try:
62 | collector.collect_container_events()
63 | except Exception as e:
64 | print(e, file=sys.stderr)
65 | time.sleep(10)
--------------------------------------------------------------------------------
/python/bootstrap.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | # The name of this file shouldn't be changed.
23 | # The purpose of this file is to avoid changing the Java code if the python file names will be changed.
24 | # This file will be executed by the com.microsoft.applicationinsights.agent.AgentBootstrapper, and will invoke the script for fetching data from Docker Remote API.
25 | import argparse
26 | import os
27 | from appinsights import program
28 |
29 | _docker_socket = 'unix:///docker.sock'
30 | _docker_info_path = '/usr/appinsights/docker/docker.info'
31 | _sdk_info_file = '/usr/appinsights/docker/sdk.info'
32 |
33 | parser = argparse.ArgumentParser(description="Application Insights container collector/injector")
34 | parser.add_argument("method", help="The method to run.", choices=['collect', 'inject', 'custom', 'events'])
35 | parser.add_argument("--script", help="The script to run when choosing 'custom' method")
36 | parser.add_argument("--collect-interval", help="The interval (in seconds) in which the performance counters are collected", default=10)
37 |
38 | args = parser.parse_args()
39 | method = args.method
40 | script = args.script
41 | collect_interval = args.collect_interval
42 |
43 | methods = {'collect': lambda: program.run_collect_performance_counters(docker_socket=_docker_socket, sdk_file=_sdk_info_file, docker_info_file=_docker_info_path, collect_interval=collect_interval),
44 | 'inject': lambda: program.run_injector(docker_socket=_docker_socket, docker_info_path=_docker_info_path),
45 | 'custom': lambda: os.system(script),
46 | 'events': lambda : program.run_collect_containers_events(docker_socket=_docker_socket, docker_info_file=_docker_info_path, sdk_file=_sdk_info_file)}
47 |
48 | assert method in methods
49 | methods[method]()
50 |
--------------------------------------------------------------------------------
/python/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
--------------------------------------------------------------------------------
/python/tests/appinsights_tests/TestDockerCollector.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | import time
23 |
24 | __author__ = 'galha'
25 |
26 | import unittest
27 | from unittest.mock import patch, MagicMock
28 | from unittest.mock import Mock, mock_open
29 | from appinsights.dockerwrapper import DockerClientWrapper, DockerWrapperError
30 | from appinsights.dockercollector import DockerCollector
31 |
32 | class TestDockerCollector(unittest.TestCase):
33 | def test_collect_and_send(self):
34 | events = []
35 | properties = {'p1':'v1','p2':'v2'}
36 | metrics = ['m1','m2','m3']
37 | containers = [{'Id':'c1','ikey':'k1'}, {'Id':'c2','ikey':'k2'}, {'Id':'c3','ikey':'k3'}]
38 | stats = ['s1','s2','s3']
39 | host_name = 'host'
40 | with patch('appinsights.dockerconvertors.get_container_properties') as properties_mock:
41 | with patch('appinsights.dockerconvertors.convert_to_metrics') as to_metric_mock:
42 | properties_mock.return_value = properties
43 | to_metric_mock.return_value = metrics
44 | wrapper_mock = Mock()
45 | wrapper_mock.get_host_name.return_value = host_name
46 | wrapper_mock.get_containers.return_value = containers
47 | wrapper_mock.get_stats.return_value = stats
48 | wrapper_mock.run_command.return_value = ''
49 | injector_mock = Mock()
50 | injector_mock.get_my_container_id.return_value = 'c1'
51 | collector = DockerCollector(wrapper_mock, injector_mock, 3, lambda x: events.append(x))
52 | collector.collect_stats_and_send()
53 | expected_metrics = [{'metric':metric, 'properties': properties} for container in containers for metric in metrics]
54 | expectedEventsCount = len(containers)*len(metrics)
55 | self.assertEqual(expectedEventsCount, len(events))
56 | for sent_event in events:
57 | self.assertIn(sent_event ,expected_metrics)
58 |
59 | def test_collect_and_send_dont_send_events_when_no_containers(self):
60 | events = []
61 | properties = {'p1':'v1','p2':'v2'}
62 | metrics = ['m1','m2','m3']
63 | containers = []
64 | stats = ['s1','s2','s3']
65 | host_name = 'host'
66 | with patch('appinsights.dockerconvertors.get_container_properties') as properties_mock:
67 | with patch('appinsights.dockerconvertors.convert_to_metrics') as to_metric_mock:
68 | properties_mock.return_value = properties
69 | to_metric_mock.return_value = metrics
70 | wrapper_mock = Mock(spec=DockerClientWrapper)
71 | wrapper_mock.get_host_name.return_value = host_name
72 | wrapper_mock.get_containers.return_value = containers
73 | wrapper_mock.get_stats.return_value = stats
74 | wrapper_mock.run_command.return_value = ''
75 | injector_mock = Mock()
76 | injector_mock.get_my_container_id.return_value = 'c1'
77 | collector = DockerCollector(wrapper_mock, injector_mock, 3, lambda x: events.append(x))
78 | collector.collect_stats_and_send()
79 | self.assertEqual(0, len(events))
80 |
81 | def test_collect_and_send_dont_send_events_when_no_metrics(self):
82 | events = []
83 | properties = {'p1':'v1','p2':'v2'}
84 | metrics = []
85 | containers = [{'Id':'c1'}, {'Id':'c2'}]
86 | stats = ['s1','s2']
87 | host_name = 'host'
88 | with patch('appinsights.dockerconvertors.get_container_properties') as properties_mock:
89 | with patch('appinsights.dockerconvertors.convert_to_metrics') as to_metric_mock:
90 | properties_mock.return_value = properties
91 | to_metric_mock.return_value = metrics
92 | wrapper_mock = Mock(spec=DockerClientWrapper)
93 | wrapper_mock.get_host_name.return_value = host_name
94 | wrapper_mock.get_containers.return_value = containers
95 | wrapper_mock.get_stats.return_value = stats
96 | wrapper_mock.run_command.return_value = ''
97 | injector_mock=Mock()
98 | injector_mock.get_my_container_id.return_value = 'c1'
99 | collector = DockerCollector(wrapper_mock, injector_mock ,3, lambda x: events.append(x))
100 | collector.collect_stats_and_send()
101 | self.assertEqual(0, len(events))
102 |
103 | def test_collect_and_send_dont_sends_only_metrics_on_the_sender_container_when_sdk_is_running(self):
104 | events = []
105 | properties = {'p1':'v1','p2':'v2'}
106 | metrics = ['m1','m2','m3']
107 | containers = [{'Id':'c1'}, {'Id':'c2'}, {'Id':'c3'}]
108 | stats = ['s1','s2','s3']
109 | host_name = 'host'
110 | with patch('appinsights.dockerconvertors.get_container_properties') as properties_mock:
111 | with patch('appinsights.dockerconvertors.convert_to_metrics') as to_metric_mock:
112 | properties_mock.return_value = properties
113 | to_metric_mock.return_value = metrics
114 | wrapper_mock = Mock(spec=DockerClientWrapper)
115 | wrapper_mock.get_host_name.return_value = host_name
116 | wrapper_mock.get_containers.return_value = containers
117 | wrapper_mock.get_stats.return_value = stats
118 | wrapper_mock.run_command.return_value = 'InstrumentationKey=ikey'
119 | injector_mock = Mock()
120 | injector_mock.get_my_container_id.return_value = 'c1'
121 | collector = DockerCollector(wrapper_mock, injector_mock,3, lambda x: events.append(x))
122 | collector.collect_stats_and_send()
123 | self.assertEqual(3, len(events))
124 | wrapper_mock.get_stats.call_with(container={'Id':'c1'})
125 | self.assertEqual(1, wrapper_mock.get_stats.call_count)
126 |
127 | def test_when_docker_wrapper_raises_on_run_commnad_it_assume_there_is_no_sdk_and_send_events(self):
128 | events = []
129 | properties = {'p1':'v1','p2':'v2'}
130 | metrics = ['m1','m2','m3']
131 | containers = [{'Id':'c1'}]
132 | stats = ['s1','s2','s3']
133 | host_name = 'host'
134 | with patch('appinsights.dockerconvertors.get_container_properties') as properties_mock:
135 | with patch('appinsights.dockerconvertors.convert_to_metrics') as to_metric_mock:
136 | properties_mock.return_value = properties
137 | to_metric_mock.return_value = metrics
138 | wrapper_mock = Mock(spec=DockerClientWrapper)
139 | wrapper_mock.get_host_name.return_value = host_name
140 | wrapper_mock.get_containers.return_value = containers
141 | wrapper_mock.get_stats.return_value = stats
142 | wrapper_mock.run_command.side_effect = DockerWrapperError('container is paused')
143 | injector_mock = Mock()
144 | injector_mock.get_my_container_id.return_value = 'c1'
145 | collector = DockerCollector(wrapper_mock, injector_mock, 3, lambda x: events.append(x))
146 | collector.collect_stats_and_send()
147 | self.assertEqual(len(metrics), len(events))
148 |
149 | def test_sender_sends_events_even_when_it_has_sdk(self):
150 | events = []
151 | properties = {'p1':'v1','p2':'v2'}
152 | metrics = ['m1','m2','m3']
153 | containers = [{'Id':'c1'}]
154 | stats = ['s1','s2','s3']
155 | host_name = 'host'
156 | with patch('appinsights.dockerconvertors.get_container_properties') as properties_mock:
157 | with patch('appinsights.dockerconvertors.convert_to_metrics') as to_metric_mock:
158 | properties_mock.return_value = properties
159 | to_metric_mock.return_value = metrics
160 | wrapper_mock = Mock(spec=DockerClientWrapper)
161 | wrapper_mock.get_host_name.return_value = host_name
162 | wrapper_mock.get_containers.return_value = containers
163 | wrapper_mock.get_stats.return_value = stats
164 | wrapper_mock.run_command.return_value = 'InstrumentationKey=ikey'
165 | injector_mock = Mock()
166 | injector_mock.get_my_container_id.return_value = 'c1'
167 | collector = DockerCollector(wrapper_mock, injector_mock, 3, lambda x: events.append(x))
168 | collector.collect_stats_and_send()
169 | self.assertEqual(len(metrics), len(events))
170 | self.assertEqual(3, len(events))
171 |
172 | def test_old_container_is_not_removed_immediately(self):
173 | new_containers = [{'Id':'c2','ikey':'k2', 'unregistered': None}]
174 | current_containers = {'c1': {'Id':'c1','ikey':'k1', 'unregistered': time.time()}}
175 |
176 | updated_containers = DockerCollector.remove_old_containers(current_containers, new_containers)
177 |
178 | self.assertEqual(current_containers['c1']['Id'], updated_containers['c1']['Id'])
179 |
180 | def test_old_container_is_removed_after_threshold(self):
181 | new_containers = [{'Id':'c2','ikey':'k2', 'unregistered': None}]
182 | current_containers = {'c1': {'Id':'c1','ikey':'k1', 'unregistered': time.time() - 70}}
183 |
184 | updated_containers = DockerCollector.remove_old_containers(current_containers, new_containers)
185 |
186 | self.assertTrue(len(updated_containers) == 0)
187 |
--------------------------------------------------------------------------------
/python/tests/appinsights_tests/TestDockerConvertors.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | __author__ = 'galha'
23 | import unittest
24 | from appinsights import dockerconvertors
25 |
26 |
27 | class TestDockerConvertors(unittest.TestCase):
28 | def test_get_total_blkio_when_total_exists(self):
29 | expected = 40
30 | stat = {'blkio_stats': {'io_service_bytes_recursive': [{'op': 'Total', 'value': expected}]}}
31 | actual = dockerconvertors.get_total_blkio(stat)
32 | self.assertEqual(expected, actual)
33 |
34 | def test_get_total_blkio_when_total_not_exists(self):
35 | expected = 0
36 | stat = {'blkio_stats': {'io_service_bytes_recursive': []}}
37 | actual = dockerconvertors.get_total_blkio(stat)
38 | self.assertEqual(expected, actual)
39 |
40 | def test_get_cpu_metric(self):
41 | samples = [(0, 0), (1, 2), (2, 4), (4, 100)]
42 | expected_avg = 34.0277778
43 | expected_min = 2.0833333
44 | expected_max = 50
45 | expected_std = 27.6647004
46 | stats = [(system, {'cpu_stats': {'cpu_usage': {'total_usage': cpu}, 'system_cpu_usage': system}}) for
47 | cpu, system in samples]
48 | metric = dockerconvertors.get_cpu_metric(stats)
49 | self.assertEqual('% Processor Time', metric['name'])
50 | self.assertEqual(len(samples) - 1, metric['count'])
51 | self.assertAlmostEqual(expected_avg, metric['value'], delta=0.01)
52 | self.assertAlmostEqual(expected_min, metric['min'], delta=0.01)
53 | self.assertAlmostEqual(expected_max, metric['max'], delta=0.01)
54 | self.assertAlmostEqual(expected_std, metric['std'], delta=0.01)
55 |
56 | def test_get_cpu_metric_trows_when_stats_is_none(self):
57 | self.assertRaises(AssertionError, dockerconvertors.get_cpu_metric, None)
58 |
59 | def test_get_cpu_metric_trows_when_stats_is_has_only_one_stat(self):
60 | stat = [(0, {'cpu_stats': {'cpu_usage': {'total_usage': 0}, 'system_cpu_usage': 0}})]
61 | self.assertRaises(AssertionError, dockerconvertors.get_cpu_metric, stat)
62 |
63 | def test_get_cpu_metric_trows_when_stats_is_empty(self):
64 | stat = []
65 | self.assertRaises(AssertionError, dockerconvertors.get_cpu_metric, stat)
66 |
67 | def test_per_second_metric(self):
68 | samples = [(0, 1), (1, 2), (2, 3), (3, 10)]
69 | expected_metric = {'name': 'm1', 'count': len(samples) - 1, 'value': 3, 'min': 1, 'max': 7, 'std': 3.464101615}
70 | actual_metric = dockerconvertors.get_per_second_metric('m1', lambda s: s, samples)
71 | self._assert_metrics_equals(expected_metric=expected_metric, actual_metric=actual_metric)
72 |
73 | def test_per_second_metric_rais_when_metric_is_none(self):
74 | self.assertRaises(AssertionError, dockerconvertors.get_per_second_metric, None, lambda s: s, [(0, 1), (2, 3)])
75 |
76 | def test_per_second_metric_rais_when_func_is_none(self):
77 | self.assertRaises(AssertionError, dockerconvertors.get_per_second_metric, "metric1", None, [(0, 1), (2, 3)])
78 |
79 | def test_per_second_metric_rais_when_stats_has_only_one_stats(self):
80 | self.assertRaises(AssertionError, dockerconvertors.get_per_second_metric, 'm1', lambda s: s, [(0, 0)])
81 |
82 | def test_per_second_metric_rais_when_stats_has_zero_stats(self):
83 | self.assertRaises(AssertionError, dockerconvertors.get_per_second_metric, 'm1', lambda s: s, [])
84 |
85 | def test_get_simple_metric(self):
86 | samples = [(0, 123), (1, 2321), (3, 2312), (4, -234)]
87 | expected_metric = {'name': 'm1', 'value': 1130.5, 'count': len(samples), 'min': -234, 'max': 2321,
88 | 'std': 1377.213249}
89 | actual_metric = dockerconvertors.get_simple_metric('m1', lambda s: s, samples)
90 | self._assert_metrics_equals(expected_metric=expected_metric, actual_metric=actual_metric)
91 |
92 | def test_get_simple_metric_rais_when_metric_is_none(self):
93 | self.assertRaises(AssertionError, dockerconvertors.get_simple_metric, None, lambda s: s, [(0, 1), (2, 3)])
94 |
95 | def test_get_simple_metric_rais_when_func_is_none(self):
96 | self.assertRaises(AssertionError, dockerconvertors.get_simple_metric, "metric1", None, [(0, 1), (2, 3)])
97 |
98 | def test_get_simple_metric_rais_when_stats_has_only_one_stats(self):
99 | self.assertRaises(AssertionError, dockerconvertors.get_simple_metric, 'm1', lambda s: s, [(0, 0)])
100 |
101 | def test_get_simple_metric_rais_when_stats_has_zero_stats(self):
102 | self.assertRaises(AssertionError, dockerconvertors.get_simple_metric, 'm1', lambda s: s, [])
103 |
104 | def _assert_metrics_equals(self, expected_metric, actual_metric):
105 | self.assertEqual(expected_metric['name'], actual_metric['name'])
106 | self.assertEqual(expected_metric['count'], actual_metric['count'])
107 | self.assertAlmostEqual(expected_metric['value'], actual_metric['value'], delta=0.001)
108 | self.assertAlmostEqual(expected_metric['min'], actual_metric['min'], delta=0.001)
109 | self.assertAlmostEqual(expected_metric['max'], actual_metric['max'], delta=0.001)
110 | self.assertAlmostEqual(expected_metric['std'], actual_metric['std'], delta=0.001)
111 |
112 | def test_convert_to_metrics(self):
113 | time_samples = [0, 10, 20, 30, 40, 50, 60]
114 | cpu_samples = [0, 1, 2, 5, 9, 19, 22]
115 | system_samples = [0, 10, 20, 30, 40, 50, 60]
116 | blkio_samples = [0, 1, 20, 30, 200, 350, 700]
117 | rx_samples = [0, 0, 0, 101, 120, 120, 230]
118 | tx_samples = [0, 0, 10, 10, 200, 250, 260]
119 | memory_samples = [1000000, 1000000, 2000000, 2020000, 3000000, 2000000, 1000000]
120 | memory_limit = 8000000
121 |
122 | samples = [(time, {'cpu_stats': {'cpu_usage': {'total_usage': cpu}, 'system_cpu_usage': system},
123 | 'memory_stats': {'limit': memory_limit, 'usage': mem}, 'network': {'rx_bytes': rx, 'tx_bytes': tx},
124 | 'blkio_stats': {'io_service_bytes_recursive': [{'op': 'Total', 'value': blkio}]}})
125 | for time, cpu, system, blkio, rx, tx, mem in
126 | zip(time_samples, cpu_samples, system_samples, blkio_samples, rx_samples, tx_samples,
127 | memory_samples)]
128 |
129 | expected_metrics = {
130 | '% Processor Time': {'name': '% Processor Time', 'count': 6, 'value': 36.66666667, 'min': 10, 'max': 100, 'std': 33.26659987},
131 | 'Available Bytes': {'name': 'Available Bytes', 'count': 7, 'value': 6282857.142857143, 'min': 5000000,
132 | 'max': 7000000, 'std': 757225.5121101482},
133 | 'Docker RX Bytes':{'name': 'Docker RX Bytes', 'count': 6, 'value': 3.833333333, 'min': 0, 'max': 11, 'std': 5.262192192},
134 | 'Docker TX Bytes': {'name': 'Docker TX Bytes', 'count': 6, 'value': 4.333333333, 'min': 0, 'max': 19, 'std': 7.420691792},
135 | 'Docker Blkio Bytes': {'name': 'Docker Blkio Bytes', 'count': 6, 'value': 11.66666667, 'min': 0.1, 'max': 35, 'std': 13.61582413}}
136 |
137 | actual_metrics = dockerconvertors.convert_to_metrics(samples)
138 |
139 | for metric in actual_metrics:
140 | self.assertTrue(metric['name'] in expected_metrics)
141 | self._assert_metrics_equals(expected_metrics[metric['name']], metric)
142 |
143 | def test_get_container_properties_from_inspect(self):
144 | expected = {'Docker host': 'host1', 'Docker image': 'image1', 'Docker container id': 'c1', 'Docker container name': 'container1'}
145 | inspect = {'Name':expected['Docker container name'], 'Id':expected['Docker container id'], 'Config':{'Image': expected['Docker image']}}
146 | properties = dockerconvertors.get_container_properties_from_inspect(inspect, expected['Docker host'])
147 | self.assertDictEqual(expected, properties)
--------------------------------------------------------------------------------
/python/tests/appinsights_tests/TestDockerInjector.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | __author__ = 'galha'
23 |
24 | import builtins
25 | from concurrent.futures.thread import ThreadPoolExecutor
26 | import time
27 | from unittest.mock import Mock, patch, mock_open
28 | import unittest
29 | from appinsights.dockerinjector import DockerInjector
30 | import re
31 |
32 |
33 | class TestDockerInjector(unittest.TestCase):
34 | def test_inject(self):
35 | expected = 'file already exists'
36 | container = {'Id': 'c1'}
37 | wrapper_mock = Mock()
38 | wrapper_mock.get_containers.return_value = [container]
39 | wrapper_mock.run_command.return_value = 'file already exists'
40 | injector = DockerInjector(docker_wrapper=wrapper_mock, docker_info_path="/path/docker.info")
41 | results = injector.inject_context()
42 | for c, result in results:
43 | self.assertEqual(container['Id'], c)
44 | self.assertEqual(expected, result)
45 |
46 | def test_get_my_container_id_when_file_exists(self):
47 | template = 'Docker container name=/boring_brattain,Docker image=ttt,Docker container id={0},Docker host=galha-ubuntu'
48 | expected_id = 'cd9d134b64807148faa24a17519c8e1a2650b825d4d38944ac54281b2dd1d94e'
49 | data = template.format(expected_id)
50 | with patch('os.path.exists') as exists:
51 | exists.return_value = True
52 | with patch.object(builtins, 'open', mock_open(read_data=data)):
53 | wrapper_mock = Mock()
54 | wrapper_mock.get_containers.return_value = [{'Id': 'c1'}]
55 | wrapper_mock.run_command.return_value = 'file already exists'
56 | injector = DockerInjector(docker_wrapper=wrapper_mock, docker_info_path="/path/docker.info")
57 | id = injector.get_my_container_id()
58 | self.assertEqual(0, wrapper_mock.run_command.call_count)
59 | self.assertEqual(expected_id, id)
60 |
61 | def test_get_my_container_id_when_file_exists_with_new_line(self):
62 | template = 'Docker container name=/boring_brattain,Docker image=ttt,Docker host=galha-ubuntu,Docker container id={0}\n'
63 | expected_id = 'cd9d134b64807148faa24a17519c8e1a2650b825d4d38944ac54281b2dd1d94e'
64 | data = template.format(expected_id)
65 | with patch('os.path.exists') as exists:
66 | exists.return_value = True
67 | with patch.object(builtins, 'open', mock_open(read_data=data)):
68 | wrapper_mock = Mock()
69 | wrapper_mock.get_containers.return_value = [{'Id': 'c1'}]
70 | wrapper_mock.run_command.return_value = 'file already exists'
71 | injector = DockerInjector(docker_wrapper=wrapper_mock, docker_info_path="/path/docker.info")
72 | id = injector.get_my_container_id()
73 | self.assertEqual(0, wrapper_mock.run_command.call_count)
74 | self.assertEqual(expected_id, id)
75 |
76 | def test_get_my_container_id_when_file_not_exists(self):
77 | template = 'Docker container name=/boring_brattain,Docker image=ttt,Docker container id={0},Docker host=galha-ubuntu'
78 | expected_id = 'cd9d134b64807148faa24a17519c8e1a2650b825d4d38944ac54281b2dd1d94e'
79 | data = template.format(expected_id)
80 | with patch('os.path.exists') as exists:
81 | exists.side_effect = [False, True]
82 | with patch.object(builtins, 'open', mock_open(read_data=data)):
83 | wrapper_mock = Mock()
84 | wrapper_mock.get_containers.return_value = [{'Id': 'c1'}]
85 | wrapper_mock.run_command.return_value = 'file already exists'
86 | injector = DockerInjector(docker_wrapper=wrapper_mock, docker_info_path="/path/docker.info")
87 | id = injector.get_my_container_id()
88 | self.assertEqual(2, wrapper_mock.run_command.call_count)
89 | self.assertEqual(expected_id, id)
90 |
91 | def test_start(self):
92 | expected_c1 = {'Docker container id': 'c1', 'Docker host': 'host', 'Docker container name': 'name1', 'Docker image': 'image1'}
93 | expected_c2 = {'Docker container id': 'c2', 'Docker host': 'host', 'Docker container name': 'name2', 'Docker image': 'image2'}
94 | container = {'Id': 'c1', 'Image':'image1', 'Names':['name1']}
95 |
96 | wrapper_mock = Mock()
97 | wrapper_mock.get_containers.return_value = [container]
98 | wrapper_mock.get_host_name.return_value='host'
99 | wrapper_mock.run_command.return_value = 'file already exists'
100 | wrapper_mock.get_events.return_value = [{'time': 1439388853, 'Id': 'c2', 'id': 'c2', 'from': 'image2', 'status': 'start'}]
101 | wrapper_mock.get_inspection.return_value = {'Id': 'c2', 'status': 'start', 'Config':{'Image':'image2'}, 'Name':'name2'}
102 |
103 | def assert_func():
104 | res = wrapper_mock.run_command.mock_calls
105 | start = time.time()
106 | while len(res) < 4 and time.time() - start < 100:
107 | time.sleep(1)
108 | self.assertEqual(4, len(res))
109 |
110 | injector = DockerInjector(docker_wrapper=wrapper_mock, docker_info_path="/path/docker.info")
111 | with ThreadPoolExecutor(max_workers=3) as ex:
112 | ex.submit(lambda: injector.start())
113 | result = ex.submit(lambda: assert_func())
114 | result.result()
115 | calls_argument_keys = [keys for name, args, keys in wrapper_mock.run_command.mock_calls]
116 | c1_calls = [d for d in calls_argument_keys if 'container' in d and 'Id' in d['container'] and d['container']['Id'] == 'c1']
117 | c2_calls = [d for d in calls_argument_keys if 'container' in d and 'Id' in d['container'] and d['container']['Id'] == 'c2']
118 | self.assertEqual(2, len(c1_calls))
119 | self.assertEqual(2, len(c2_calls))
120 | c1_data = c1_calls[1]['cmd']
121 | m1 = re.search('printf \'(.+)\'', c1_data)
122 | self.assertTrue(m1)
123 | actual_c1= {key:val for key,val in [token.split('=') for token in m1.group(1).strip(' ').split(',')]}
124 | self.assertDictEqual(expected_c1, actual_c1)
125 | c2_data = c2_calls[1]['cmd']
126 | m2 = re.search('printf \'(.+)\'', c2_data)
127 | self.assertTrue(m2)
128 | actual_c2= {key:val for key,val in [token.split('=') for token in m2.group(1).strip(' ').split(',')]}
129 | self.assertDictEqual(expected_c2, actual_c2)
--------------------------------------------------------------------------------
/python/tests/appinsights_tests/TestDockerWrapper.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | from requests.packages.urllib3.exceptions import ReadTimeoutError
23 |
24 | __author__ = 'galha'
25 | import unittest
26 | from appinsights.dockerwrapper import DockerClientWrapper, DockerWrapperError, get_production_docker_wrapper, \
27 | ProductionWrapper
28 | from unittest.mock import Mock, patch, call
29 | from docker import Client
30 | from docker.errors import APIError
31 |
32 |
33 | class TestDockerClientWrapper(unittest.TestCase):
34 | def test_constructor_raise_when_docker_client_is_none(self):
35 | self.assertRaises(AssertionError, DockerClientWrapper, None)
36 |
37 | def test_get_host_name_gets_the_client_host_name(self):
38 | expecte_host_name = 'host'
39 | mock = Mock(spec=Client)
40 | mock.info.return_value = {'Name': expecte_host_name}
41 | wrapper = DockerClientWrapper(mock)
42 | actual = wrapper.get_host_name()
43 | self.assertEqual(expecte_host_name, actual)
44 |
45 | def test_get_host_name_returns_na_when_no_host_name_found(self):
46 | expecte_host_name = 'N/A'
47 | mock = Mock(spec=Client)
48 | mock.info.return_value = {}
49 | wrapper = DockerClientWrapper(mock)
50 | actual = wrapper.get_host_name()
51 | self.assertEqual(expecte_host_name, actual)
52 |
53 | def test_get_containers_gets_the_client_containers(self):
54 | expected_containers = ['c1', 'c2', 'c3']
55 | mock = Mock(spec=Client)
56 | mock.containers.return_value = expected_containers
57 | wrapper = DockerClientWrapper(mock)
58 | actual = wrapper.get_containers()
59 | self.assertEqual(expected_containers, actual)
60 |
61 | def test_get_stats_gets_the_client_stats(self):
62 | expected_stats = ['s1', 's2', 's3']
63 | mock = Mock(spec=Client)
64 | mock.stats.return_value = expected_stats
65 | wrapper = DockerClientWrapper(mock)
66 | actual = wrapper.get_stats('c1', 3)
67 | self.assertEqual(expected_stats, [stat for time, stat in actual])
68 |
69 | def test_get_stats_gets_requested_number_of_stats_from_the_client(self):
70 | mock = Mock(spec=Client)
71 | mock.stats.return_value = map(lambda i: "s{0}".format(i), range(0, 100000))
72 | expected = ["s0", "s1", "s2"]
73 | wrapper = DockerClientWrapper(mock)
74 | actual = wrapper.get_stats('c1', 3)
75 | self.assertEqual(expected, [stat for time, stat in actual])
76 |
77 | def test_get_stats_return_empty_list_on_api_error(self):
78 | mock = Mock()
79 | mock.stats.side_effect = APIError("boom", "boom", "boom")
80 | wrapper = DockerClientWrapper(mock)
81 | actual = wrapper.get_stats('c1', 3)
82 | self.assertEqual([], [stat for time, stat in actual])
83 |
84 | def test_get_stats_return_empty_list_on_ReadTimeoutError(self):
85 | mock = Mock()
86 | mock.stats.side_effect = ReadTimeoutError('pool', 'url', 'message')
87 | wrapper = DockerClientWrapper(mock)
88 | actual = wrapper.get_stats('c1', 3)
89 | self.assertEqual([], [stat for time, stat in actual])
90 |
91 | def test_get_stats_return_empty_list_on_timeout(self):
92 | mock = Mock()
93 | mock.stats.side_effect = ReadTimeoutError('pool', 'url', 'message')
94 | wrapper = DockerClientWrapper(mock)
95 | actual = wrapper.get_stats('c1', 3)
96 | self.assertEqual([], [stat for time, stat in actual])
97 |
98 | def test_run_command(self):
99 | expectedResult = b'result'
100 | mock = Mock()
101 | mock.exec_create.return_value = 'exec1'
102 | mock.exec_start.return_value = expectedResult
103 | wrapper = DockerClientWrapper(mock)
104 | result = wrapper.run_command('c1', 'ls')
105 | self.assertEqual(expectedResult.decode('utf-8'), result)
106 |
107 | def test_run_command_raise_docker_wrapper_error(self):
108 | expectedResult = b'result'
109 | mock = Mock()
110 | mock.exec_create.side_effect = ReadTimeoutError('pool', 'url', 'message')
111 | mock.exec_start.return_value = expectedResult
112 | wrapper = DockerClientWrapper(mock)
113 | self.assertRaises(DockerWrapperError, wrapper.run_command, 'c1', 'ls')
114 |
115 | def test_production_wrapper(self):
116 | with patch('appinsights.dockerwrapper.ProductionWrapper') as mock:
117 | get_production_docker_wrapper("unix://docker.sock")
118 | self.assertEqual(1, mock.call_count)
119 | mock.assert_has_calls([call(base_url="unix://docker.sock")])
120 |
121 | def test_production_wrapper_uses_two_clients(self):
122 | with patch('appinsights.dockerwrapper.DockerClientWrapper') as mock:
123 | m1, m2 = Mock(), Mock()
124 | mock.side_effect = [m1, m2]
125 | wrapper = ProductionWrapper("unix://docker.sock")
126 | self.assertEqual(2, mock.call_count)
127 | wrapper.get_containers()
128 | wrapper.run_command(None, None)
129 | m1c = m1.get_containers.call_count
130 | m1r = m1.run_command.call_count
131 | m2c = m2.get_containers.call_count
132 | m2r = m2.run_command.call_count
133 | self.assertEqual(1, m1c + m1r)
134 | self.assertEqual(1, m2c + m2r)
135 | self.assertEqual(1, m1c + m2c)
136 | self.assertEqual(1, m1r + m2r)
137 |
138 | def test_get_inspect(self):
139 | expectedResult = 'result'
140 | mock = Mock()
141 | mock.inspect_container.return_value = expectedResult
142 | wrapper = DockerClientWrapper(mock)
143 | inspection = wrapper.get_inspection({'Id':'c1'})
144 | self.assertEqual(expectedResult, inspection)
145 |
146 |
--------------------------------------------------------------------------------
/python/tests/appinsights_tests/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # ApplicationInsights-Docker
3 | # Copyright (c) Microsoft Corporation
4 | # All rights reserved.
5 | #
6 | # MIT License
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | # software and associated documentation files (the ""Software""), to deal in the Software
9 | # without restriction, including without limitation the rights to use, copy, modify, merge,
10 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | # persons to whom the Software is furnished to do so, subject to the following conditions:
12 | # The above copyright notice and this permission notice shall be included in all copies or
13 | # substantial portions of the Software.
14 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | # DEALINGS IN THE SOFTWARE.
20 | #
--------------------------------------------------------------------------------
/sdk/ApplicationInsights.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 | False
32 |
33 |
34 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'ApplicationInsights-Docker'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/AgentBootstrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights;
23 |
24 | import com.microsoft.applicationinsights.common.ApplicationInsightsSender;
25 | import com.microsoft.applicationinsights.agent.DockerAgent;
26 | import com.microsoft.applicationinsights.agent.DockerContainerContextAgent;
27 | import com.microsoft.applicationinsights.python.ContainerContextPythonBoostrapper;
28 | import com.microsoft.applicationinsights.python.ContainerStatePythonBootstrapper;
29 | import com.microsoft.applicationinsights.python.MetricCollectionPythonBoostrapper;
30 | import com.microsoft.applicationinsights.python.PythonBootstrapper;
31 |
32 | import java.io.IOException;
33 | import java.util.HashMap;
34 |
35 | /**
36 | * Created by yonisha on 7/22/2015.
37 | *
38 | * The agent is executed by the Docker ENTRYPOINT command.
39 | */
40 | public class AgentBootstrapper {
41 |
42 | // region Consts
43 |
44 | private static final int DEFAULT_COLLECT_INTERVAL = 45;
45 | private static final String CONTAINER_USAGE_COMMAND =
46 | "docker run -v /var/run/docker.sock:/docker.sock -d microsoft/applicationinsights ikey=";
47 |
48 | // endregion Consts
49 |
50 | // region Public
51 |
52 | public static void main(String[] args) throws IOException, InterruptedException {
53 | System.out.println("Starting Application Insights Docker agent.");
54 | HashMap argumentsMap = parseArguments(args);
55 |
56 | String instrumentationKey = argumentsMap.get("ikey");
57 | if (instrumentationKey == null) {
58 | System.out.println("Usage: " + CONTAINER_USAGE_COMMAND);
59 |
60 | return;
61 | }
62 |
63 | // TODO: Create object for argument verification (type, existence, default value etc.)
64 | ApplicationInsightsSender applicationInsightsSender = new ApplicationInsightsSender(instrumentationKey);
65 |
66 | int sampleRateFromArgument = getSampleRateFromArgument(argumentsMap);
67 | PythonBootstrapper metricCollectionBootstrapper = new MetricCollectionPythonBoostrapper(sampleRateFromArgument);
68 | Thread metricCollectionAgentThread = createMetricCollectionProcess(applicationInsightsSender, metricCollectionBootstrapper);
69 |
70 | PythonBootstrapper containerStateBootstrapper = new ContainerStatePythonBootstrapper();
71 | Thread containerStateThread = createContainerStateProcess(applicationInsightsSender, containerStateBootstrapper);
72 |
73 | Thread containerContextAgentThread = createContainerContextProcess();
74 |
75 | AgentBootstrapper agentBootstrapper = new AgentBootstrapper();
76 | agentBootstrapper.run(applicationInsightsSender, metricCollectionAgentThread, containerStateThread, containerContextAgentThread);
77 |
78 | System.out.println("Shutting down Application Insights Docker agent.");
79 | }
80 |
81 | // TODO: move to a dedicate object
82 | private static int getSampleRateFromArgument(HashMap argumentsMap) {
83 | final String intervalArgument = "collect-interval";
84 | String sampleIntervalStr = argumentsMap.get(intervalArgument);
85 |
86 | int sampleInterval = DEFAULT_COLLECT_INTERVAL;
87 | if (sampleIntervalStr != null) {
88 | try {
89 | sampleInterval = (int)Double.parseDouble(sampleIntervalStr);
90 | } catch (NumberFormatException e) {
91 | System.err.print("Failed to parse '" + sampleIntervalStr + "' as '" + intervalArgument + "' argument - must be a number.");
92 | }
93 | } else {
94 | System.out.println("No collect interval argument provided.");
95 | }
96 |
97 | System.out.println("Collect interval is set to " + sampleInterval + " seconds.");
98 |
99 | return sampleInterval;
100 | }
101 |
102 | public void run(
103 | ApplicationInsightsSender applicationInsightsSender,
104 | Thread metricCollectionAgentThread,
105 | Thread containerStateThread,
106 | Thread containerContextAgentThread) throws InterruptedException {
107 |
108 | // Starting threads.
109 | metricCollectionAgentThread.start();
110 | containerContextAgentThread.start();
111 | containerStateThread.start();
112 |
113 | // Waiting for all threads.
114 | metricCollectionAgentThread.join();
115 | containerStateThread.join();
116 | containerContextAgentThread.join();
117 | }
118 |
119 | // endregion public
120 |
121 | // region Private
122 |
123 | private static HashMap parseArguments(String[] args) {
124 | HashMap arguments = new HashMap();
125 |
126 | for (String arg : args) {
127 | String[] kv = arg.split("=");
128 | if (kv.length == 2) {
129 | arguments.put(kv[0], kv[1]);
130 | }
131 | }
132 |
133 | return arguments;
134 | }
135 |
136 | protected static Thread createMetricCollectionProcess(ApplicationInsightsSender aiSender, PythonBootstrapper metricCollectionBootstrapper) {
137 | System.out.println("Starting metric collection process.");
138 | DockerAgent dockerMetricAgent = new DockerAgent(metricCollectionBootstrapper, aiSender);
139 | Thread metricCollectionAgentThread = new Thread(dockerMetricAgent);
140 | metricCollectionAgentThread.setDaemon(true);
141 |
142 | return metricCollectionAgentThread;
143 | }
144 |
145 | protected static Thread createContainerContextProcess() {
146 | System.out.println("Starting container context process.");
147 | PythonBootstrapper containerContextBootstrapper = new ContainerContextPythonBoostrapper();
148 | DockerContainerContextAgent containerContextAgent = new DockerContainerContextAgent(containerContextBootstrapper);
149 | Thread containerContextAgentThread = new Thread(containerContextAgent);
150 | containerContextAgentThread.setDaemon(true);
151 |
152 | return containerContextAgentThread;
153 | }
154 |
155 | protected static Thread createContainerStateProcess(ApplicationInsightsSender aiSender, PythonBootstrapper containerStateBootstrapper) {
156 | System.out.println("Starting container state process.");
157 | DockerAgent containerStateAgent = new DockerAgent(containerStateBootstrapper, aiSender);
158 | Thread containerStateThread = new Thread(containerStateAgent);
159 | containerStateThread.setDaemon(true);
160 |
161 | return containerStateThread;
162 | }
163 |
164 | // endregion Private
165 | }
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/agent/DockerAgent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.agent;
23 |
24 | import com.microsoft.applicationinsights.common.ApplicationInsightsSender;
25 | import com.microsoft.applicationinsights.providers.EventProvider;
26 | import com.microsoft.applicationinsights.python.PythonBootstrapper;
27 |
28 | import java.io.IOException;
29 |
30 | /**
31 | * Created by yonisha on 8/5/2015.
32 | */
33 | public class DockerAgent implements Runnable {
34 | private ApplicationInsightsSender applicationInsightsSender;
35 | private PythonBootstrapper pythonBootstrapper;
36 | private boolean shouldStop = false;
37 |
38 | // region Ctor
39 |
40 | public DockerAgent(PythonBootstrapper pythonBootstrapper, ApplicationInsightsSender applicationInsightsSender) {
41 | this.applicationInsightsSender = applicationInsightsSender;
42 | this.pythonBootstrapper = pythonBootstrapper;
43 | }
44 |
45 | // endregion Ctor
46 |
47 | // region Public
48 |
49 | /**
50 | * This method starts a Python process using the provided bootstrapper.
51 | * If, for any reason, the Python process exits, we start another process and continue to collect events.
52 | */
53 | public void run() {
54 |
55 | // TODO: check python exit code and check if killed intentionally.
56 | while (!shouldStop && this.pythonBootstrapper.getExitValue() != 0) {
57 |
58 | try {
59 | this.pythonBootstrapper.start(false);
60 | } catch (IOException e) {}
61 |
62 | EventProvider eventProvider = (EventProvider)this.pythonBootstrapper.getResult();
63 | if (eventProvider != null) {
64 | collectAndSendEvents(eventProvider, this.applicationInsightsSender);
65 | }
66 |
67 | String processExitInfo = this.pythonBootstrapper.getProcessExitInfo();
68 | System.out.println(processExitInfo);
69 | }
70 | }
71 |
72 | // endregion Public
73 |
74 | // region Private
75 |
76 | protected void stop() {
77 | this.shouldStop = true;
78 | }
79 |
80 | private void collectAndSendEvents(EventProvider eventProvider, ApplicationInsightsSender applicationInsightsSender) {
81 | System.out.println("Starting to collect events from: " + this.pythonBootstrapper.getClass().getSimpleName());
82 |
83 | while (true) {
84 | T event = eventProvider.getNext();
85 |
86 | // Event can be null in two cases:
87 | // 1) The underlying JSON is corrupted and cannot be serialized. In that case, make sure only event JSONs
88 | // strings are printed to the STDOUT in the python scripts. Any other user traces are not allowed.
89 | // 2) The Python process has exited. In that case, the agent will start another Python process.
90 | if (event == null) {
91 | if (!this.pythonBootstrapper.isAlive()) {
92 | break;
93 | } else {
94 | continue;
95 | }
96 | }
97 |
98 | applicationInsightsSender.track(event);
99 | }
100 | }
101 |
102 | // endregion Private
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/agent/DockerContainerContextAgent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.agent;
23 |
24 | import com.microsoft.applicationinsights.python.PythonBootstrapper;
25 |
26 | import java.io.IOException;
27 |
28 | /**
29 | * Created by yonisha on 7/28/2015.
30 | */
31 | public class DockerContainerContextAgent implements Runnable {
32 | private PythonBootstrapper pythonBootstrapper;
33 | private boolean shouldStop = false;
34 |
35 | // region Ctor
36 |
37 | public DockerContainerContextAgent(PythonBootstrapper pythonBootstrapper) {
38 | this.pythonBootstrapper = pythonBootstrapper;
39 | }
40 |
41 | // endregion Ctor
42 |
43 | // region Public
44 |
45 | public void run() {
46 |
47 | // TODO: check python exit code and check if killed intentionally.
48 | while (!shouldStop && this.pythonBootstrapper.getExitValue() != 0) {
49 | try {
50 | this.pythonBootstrapper.start(true);
51 | } catch (IOException e) {
52 | String simpleName = this.pythonBootstrapper.getClass().getSimpleName();
53 | System.out.println(simpleName + " failed with exception: " + e.getMessage());
54 |
55 | String processExitInfo = this.pythonBootstrapper.getProcessExitInfo();
56 | System.out.println(processExitInfo);
57 | }
58 | }
59 | }
60 |
61 | // endregion Public
62 |
63 | // region Private
64 |
65 | protected void stop() {
66 | this.shouldStop = true;
67 | }
68 |
69 | // endregion Private
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/common/ApplicationInsightsSender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.common;
23 |
24 | import com.microsoft.applicationinsights.TelemetryClient;
25 | import com.microsoft.applicationinsights.TelemetryConfiguration;
26 | import com.microsoft.applicationinsights.contracts.ContainerStateEvent;
27 | import com.microsoft.applicationinsights.contracts.ContainerStatsMetric;
28 | import com.microsoft.applicationinsights.telemetry.Telemetry;
29 |
30 | /**
31 | * Created by yonisha on 7/23/2015.
32 | */
33 | public class ApplicationInsightsSender {
34 | private TelemetryClient telemetryClient;
35 | private TelemetryFactory telemetryFactory;
36 |
37 | // region Ctor
38 |
39 | // Ctor for testability purposes.
40 | protected ApplicationInsightsSender(TelemetryClient telemetryClient, TelemetryFactory telemetryFactory) {
41 | this.telemetryClient = telemetryClient;
42 | this.telemetryFactory = telemetryFactory;
43 | }
44 |
45 | public ApplicationInsightsSender(String instrumentationKey) {
46 | this(initializeTelemetryClient(instrumentationKey), new TelemetryFactory());
47 | }
48 |
49 | // endregion Ctor
50 |
51 | // region Public
52 |
53 | public void track(T metric) {
54 | Telemetry telemetry;
55 |
56 | if (metric instanceof ContainerStatsMetric) {
57 | telemetry = this.telemetryFactory.createMetricTelemetry((ContainerStatsMetric) metric);
58 | } else if (metric instanceof ContainerStateEvent) {
59 | telemetry = this.telemetryFactory.createEventTelemetry((ContainerStateEvent) metric);
60 | } else {
61 | System.err.println("Unknown metric: " + metric.getClass().getSimpleName());
62 |
63 | return;
64 | }
65 |
66 | this.telemetryClient.track(telemetry);
67 | }
68 |
69 | // endregion Public
70 |
71 | // region Private
72 |
73 | private static TelemetryClient initializeTelemetryClient(String instrumentationKey) {
74 | TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.getActive();
75 | telemetryConfiguration.setInstrumentationKey(instrumentationKey);
76 | return new TelemetryClient(telemetryConfiguration);
77 | }
78 |
79 | // endregion Private
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/common/ArrayUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.common;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Arrays;
26 | import java.util.List;
27 |
28 | /**
29 | * Created by yonisha on 8/3/2015.
30 | */
31 | public class ArrayUtils {
32 | public static String[] addFirst(String string, String[] current) {
33 | List strings = new ArrayList(Arrays.asList(current));
34 | strings.add(0, string);
35 |
36 | return strings.toArray(new String[strings.size()]);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/common/Constants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.common;
23 |
24 | /**
25 | * Created by yonisha on 8/12/2015.
26 | */
27 | public class Constants {
28 | public static final String DOCKER_HOST_PROPERTY_KEY = "Docker host";
29 | public static final String DOCKER_IMAGE_PROPERTY_KEY = "Docker image";
30 | public static final String DOCKER_CONTAINER_NAME_PROPERTY_KEY = "Docker container name";
31 | public static final String DOCKER_CONTAINER_ID_PROPERTY_KEY = "Docker container id";
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/common/StringUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.common;
23 |
24 | /**
25 | * Created by yonisha on 8/31/2015.
26 | */
27 | public class StringUtils {
28 | public static boolean isNullOrEmpty(String str) {
29 | return str == null || str.isEmpty();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/common/TelemetryFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.common;
23 |
24 | import com.microsoft.applicationinsights.contracts.ContainerStateEvent;
25 | import com.microsoft.applicationinsights.contracts.ContainerStatsMetric;
26 | import com.microsoft.applicationinsights.telemetry.EventTelemetry;
27 | import com.microsoft.applicationinsights.telemetry.MetricTelemetry;
28 | import com.microsoft.applicationinsights.telemetry.PerformanceCounterTelemetry;
29 | import com.microsoft.applicationinsights.telemetry.Telemetry;
30 |
31 | import java.util.Map;
32 |
33 | /**
34 | * Created by yonisha on 8/12/2015.
35 | */
36 | public class TelemetryFactory {
37 |
38 | // region Public
39 |
40 | public Telemetry createEventTelemetry(ContainerStateEvent stateEvent) {
41 | EventTelemetry telemetry = new EventTelemetry(stateEvent.getName());
42 |
43 | if (!StringUtils.isNullOrEmpty(stateEvent.getInstrumentationKey())) {
44 | telemetry.getContext().setInstrumentationKey(stateEvent.getInstrumentationKey());
45 | }
46 |
47 | // Setting operation in order to be able to correlate events related to the same container.
48 | String containerId = stateEvent.getProperties().get(com.microsoft.applicationinsights.common.Constants.DOCKER_CONTAINER_ID_PROPERTY_KEY);
49 | telemetry.getContext().getOperation().setId(containerId);
50 | telemetry.getContext().getOperation().setName(stateEvent.getName());
51 |
52 | telemetry.getProperties().putAll(stateEvent.getProperties());
53 |
54 | return telemetry;
55 | }
56 |
57 | public Telemetry createMetricTelemetry(ContainerStatsMetric containerStatsMetric) {
58 | Telemetry telemetry;
59 | String metricName = containerStatsMetric.getMetricName();
60 |
61 | // If the given metric is one of the build-in PC in Ibiza, we track it as a performance counter telemetry.
62 | // Otherwise the given metric is sent as a custom metric.
63 | if (metricName.equalsIgnoreCase(com.microsoft.applicationinsights.internal.perfcounter.Constants.CPU_PC_COUNTER_NAME) || metricName.equalsIgnoreCase(com.microsoft.applicationinsights.internal.perfcounter.Constants.TOTAL_MEMORY_PC_COUNTER_NAME)) {
64 | telemetry = createPerformanceCounterTelemetry(containerStatsMetric);
65 | } else {
66 | MetricTelemetry metricTelemetry = new MetricTelemetry(metricName, containerStatsMetric.getValue());
67 | metricTelemetry.setMin(containerStatsMetric.getMin());
68 | metricTelemetry.setMax(containerStatsMetric.getMax());
69 | metricTelemetry.setCount(containerStatsMetric.getCount());
70 | metricTelemetry.setStandardDeviation(containerStatsMetric.getStdDev());
71 |
72 | telemetry = metricTelemetry;
73 | }
74 |
75 | Map properties = telemetry.getProperties();
76 | properties.put(com.microsoft.applicationinsights.common.Constants.DOCKER_HOST_PROPERTY_KEY, containerStatsMetric.getDockerHost());
77 | properties.put(com.microsoft.applicationinsights.common.Constants.DOCKER_IMAGE_PROPERTY_KEY, containerStatsMetric.getDockerImage());
78 | properties.put(com.microsoft.applicationinsights.common.Constants.DOCKER_CONTAINER_NAME_PROPERTY_KEY, containerStatsMetric.getDockerContainerName());
79 | properties.put(com.microsoft.applicationinsights.common.Constants.DOCKER_CONTAINER_ID_PROPERTY_KEY, containerStatsMetric.getDockerContainerId());
80 |
81 | return telemetry;
82 | }
83 |
84 | private PerformanceCounterTelemetry createPerformanceCounterTelemetry(ContainerStatsMetric containerStatsMetric) {
85 | PerformanceCounterTelemetry performanceCounterTelemetry = null;
86 |
87 | String metricName = containerStatsMetric.getMetricName();
88 | if (metricName.equalsIgnoreCase(com.microsoft.applicationinsights.internal.perfcounter.Constants.CPU_PC_COUNTER_NAME)) {
89 | performanceCounterTelemetry = new PerformanceCounterTelemetry(
90 | com.microsoft.applicationinsights.internal.perfcounter.Constants.TOTAL_CPU_PC_CATEGORY_NAME,
91 | com.microsoft.applicationinsights.internal.perfcounter.Constants.CPU_PC_COUNTER_NAME,
92 | com.microsoft.applicationinsights.internal.perfcounter.Constants.INSTANCE_NAME_TOTAL,
93 | containerStatsMetric.getValue());
94 | } else if (metricName.equalsIgnoreCase(com.microsoft.applicationinsights.internal.perfcounter.Constants.TOTAL_MEMORY_PC_COUNTER_NAME)) {
95 | performanceCounterTelemetry = new PerformanceCounterTelemetry(
96 | com.microsoft.applicationinsights.internal.perfcounter.Constants.TOTAL_MEMORY_PC_CATEGORY_NAME,
97 | com.microsoft.applicationinsights.internal.perfcounter.Constants.TOTAL_MEMORY_PC_COUNTER_NAME,
98 | "",
99 | containerStatsMetric.getValue());
100 | }
101 |
102 | return performanceCounterTelemetry;
103 | }
104 |
105 | // endregion Public
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/contracts/ContainerStateEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.contracts;
23 |
24 | import com.google.gson.JsonElement;
25 | import com.google.gson.JsonObject;
26 | import com.google.gson.JsonParser;
27 |
28 | import java.util.HashMap;
29 | import java.util.Map;
30 |
31 | /**
32 | * Created by yonisha on 8/5/2015.
33 | */
34 | public class ContainerStateEvent {
35 |
36 | // region Members
37 |
38 | private String eventName;
39 | private String ikey;
40 | private Map properties = new HashMap();
41 |
42 | // endregion Members
43 |
44 | // region Ctor
45 |
46 | public ContainerStateEvent(String json) {
47 | this.deserialize(json);
48 | }
49 |
50 | // endregion Ctor
51 |
52 | // region Public
53 |
54 | public String getName() {
55 | return this.eventName;
56 | }
57 |
58 | public Map getProperties() {
59 | return this.properties;
60 | }
61 |
62 | public String getInstrumentationKey() {
63 | return ikey;
64 | }
65 |
66 | // endregion Public
67 |
68 | // region Private
69 |
70 | private void deserialize(String json) {
71 | JsonObject jsonObj = new JsonParser().parse(json).getAsJsonObject();
72 | this.eventName = jsonObj.get("name").getAsString();
73 | this.ikey = jsonObj.get("ikey").getAsString();
74 |
75 | JsonObject propertiesObject = jsonObj.getAsJsonObject("properties");
76 |
77 | for (Map.Entry kv : propertiesObject.entrySet()) {
78 | this.properties.put(kv.getKey(), kv.getValue().getAsString());
79 | }
80 | }
81 |
82 | // endregion Private
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/contracts/ContainerStatsMetric.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.contracts;
23 |
24 | import com.google.gson.JsonObject;
25 | import com.google.gson.JsonParser;
26 | import com.microsoft.applicationinsights.common.Constants;
27 |
28 | /**
29 | * Created by yonisha on 7/22/2015.
30 | */
31 | public class ContainerStatsMetric {
32 |
33 | // region Members
34 |
35 | private String dockerHost;
36 | private String dockerImage;
37 | private String dockerContainerName;
38 | private String dockerContainerId;
39 |
40 | private String metricName;
41 | private double value;
42 | private int count;
43 | private double min;
44 | private double max;
45 | private double stdDev;
46 |
47 | // endregion Members
48 |
49 | // region Ctor
50 |
51 | public ContainerStatsMetric(String json) {
52 | deserialize(json);
53 | }
54 |
55 | // endregion Ctor
56 |
57 | // region Public Methods
58 |
59 | public String getDockerHost() {
60 | return dockerHost;
61 | }
62 |
63 | public String getDockerImage() {
64 | return dockerImage;
65 | }
66 |
67 | public String getDockerContainerName() {
68 | return dockerContainerName;
69 | }
70 |
71 | public String getDockerContainerId() {
72 | return dockerContainerId;
73 | }
74 |
75 | public String getMetricName() {
76 | return metricName;
77 | }
78 |
79 | public double getValue() {
80 | return value;
81 | }
82 |
83 | public int getCount() {
84 | return count;
85 | }
86 |
87 | public double getMin() {
88 | return min;
89 | }
90 |
91 | public double getMax() {
92 | return max;
93 | }
94 |
95 | public double getStdDev() {
96 | return stdDev;
97 | }
98 |
99 | // endregion Public Methods
100 |
101 | // region Private Methods
102 |
103 | private void deserialize(String json) {
104 | JsonObject jsonObj = new JsonParser().parse(json).getAsJsonObject();
105 | JsonObject metricJson = jsonObj.getAsJsonObject("metric");
106 |
107 | this.metricName = metricJson.get("name").getAsString();
108 | this.value = metricJson.get("value").getAsDouble();
109 | this.count = metricJson.get("count").getAsInt();
110 | this.min = metricJson.get("min").getAsDouble();
111 | this.max = metricJson.get("max").getAsDouble();
112 | this.stdDev = metricJson.get("std").getAsDouble();
113 |
114 | JsonObject propertiesJson = jsonObj.getAsJsonObject("properties");
115 | this.dockerHost = propertiesJson.get(Constants.DOCKER_HOST_PROPERTY_KEY).getAsString();
116 | this.dockerImage = propertiesJson.get(Constants.DOCKER_IMAGE_PROPERTY_KEY).getAsString();
117 | this.dockerContainerName = propertiesJson.get(Constants.DOCKER_CONTAINER_NAME_PROPERTY_KEY).getAsString();
118 | this.dockerContainerId = propertiesJson.get(Constants.DOCKER_CONTAINER_ID_PROPERTY_KEY).getAsString();
119 | }
120 |
121 | // endregion Private Methods
122 | }
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/providers/EventProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.providers;
23 |
24 | import java.io.BufferedReader;
25 | import java.io.IOException;
26 |
27 | /**
28 | * Created by yonisha on 8/5/2015.
29 | */
30 | public abstract class EventProvider {
31 |
32 | private BufferedReader inputBuffer;
33 |
34 | public EventProvider(BufferedReader inputBuffer) {
35 | this.inputBuffer = inputBuffer;
36 | }
37 |
38 | public T getNext() {
39 | String json;
40 |
41 | try {
42 | json = inputBuffer.readLine();
43 | } catch (IOException e) {
44 | System.out.println("Failed to read event from input stream with exception: " + e.getMessage());
45 |
46 | return null;
47 | }
48 |
49 | if (json == null || json.isEmpty()) {
50 | return null;
51 | }
52 |
53 | return deserialize(json);
54 | }
55 |
56 | // TODO: Implement a generic method and remove inherited classes.
57 | protected abstract T deserialize(String json);
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/providers/MetricProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.providers;
23 |
24 | import com.google.gson.JsonSyntaxException;
25 | import com.microsoft.applicationinsights.contracts.ContainerStatsMetric;
26 |
27 | import java.io.BufferedReader;
28 |
29 | /**
30 | * Created by yonisha on 7/23/2015.
31 | */
32 | public class MetricProvider extends EventProvider {
33 |
34 | public MetricProvider(BufferedReader inputBuffer) {
35 | super(inputBuffer);
36 | }
37 |
38 | protected ContainerStatsMetric deserialize(String json) {
39 | ContainerStatsMetric containerStatsMetric = null;
40 |
41 | try {
42 | containerStatsMetric = new ContainerStatsMetric(json);
43 | } catch (JsonSyntaxException e) {
44 | System.out.println("Failed to deserialize JSON to container metric: " + json);
45 | }
46 |
47 | return containerStatsMetric;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/providers/StateProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.providers;
23 |
24 | import com.google.gson.JsonSyntaxException;
25 | import com.microsoft.applicationinsights.contracts.ContainerStateEvent;
26 | import com.microsoft.applicationinsights.contracts.ContainerStatsMetric;
27 |
28 | import java.io.BufferedReader;
29 |
30 | /**
31 | * Created by yonisha on 8/5/2015.
32 | */
33 | public class StateProvider extends EventProvider {
34 |
35 | // region Ctor
36 |
37 | public StateProvider(BufferedReader bufferedReader) {
38 | super(bufferedReader);
39 | }
40 |
41 | // endregion Ctor
42 |
43 | // region Private
44 |
45 | @Override
46 | protected ContainerStateEvent deserialize(String json) {
47 | ContainerStateEvent containerStateEvent = null;
48 |
49 | try {
50 | containerStateEvent = new ContainerStateEvent(json);
51 | } catch (JsonSyntaxException e) {
52 | System.out.println("Failed to deserialize JSON to container state: " + json);
53 | }
54 |
55 | return containerStateEvent;
56 | }
57 |
58 | // endregion Private
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/python/ContainerContextPythonBoostrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.python;
23 |
24 | /**
25 | * Created by yonisha on 7/28/2015.
26 | */
27 | public class ContainerContextPythonBoostrapper extends PythonBootstrapper {
28 |
29 | private static final String BOOTSTRAPPER_ARG = "inject";
30 |
31 | public ContainerContextPythonBoostrapper() {
32 | super(BOOTSTRAPPER_ARG);
33 | }
34 |
35 | @Override
36 | public Object getResult() {
37 | return null;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/python/ContainerStatePythonBootstrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.python;
23 |
24 | import java.io.BufferedReader;
25 | import java.io.InputStreamReader;
26 |
27 | import com.microsoft.applicationinsights.providers.StateProvider;
28 |
29 | /**
30 | * Created by yonisha on 8/5/2015.
31 | */
32 | public class ContainerStatePythonBootstrapper extends PythonBootstrapper {
33 |
34 | // region Members
35 |
36 | private static final String BOOTSTRAPPER_ARG = "events";
37 |
38 | // endregion Members
39 |
40 | // region Ctors
41 |
42 | public ContainerStatePythonBootstrapper(String... bootstrapperParams) {
43 | super(bootstrapperParams);
44 | }
45 |
46 | public ContainerStatePythonBootstrapper() {
47 | this(BOOTSTRAPPER_ARG);
48 | }
49 |
50 | // endregion Ctors
51 |
52 | // region Public
53 |
54 | @Override
55 | public StateProvider getResult() {
56 | return new StateProvider(new BufferedReader(new InputStreamReader(process.getInputStream())));
57 | }
58 |
59 | // endregion Public
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/python/MetricCollectionPythonBoostrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.python;
23 |
24 | import com.microsoft.applicationinsights.providers.MetricProvider;
25 |
26 | import java.io.BufferedReader;
27 | import java.io.InputStreamReader;
28 |
29 | /**
30 | * Created by yonisha on 7/28/2015.
31 | */
32 | public class MetricCollectionPythonBoostrapper extends PythonBootstrapper {
33 |
34 | private static final String BOOTSTRAPPER_ARG = "collect";
35 |
36 | // region Ctors
37 |
38 | protected MetricCollectionPythonBoostrapper(ProcessBuilder processBuilder) {
39 | super(processBuilder);
40 | }
41 |
42 | public MetricCollectionPythonBoostrapper(String... bootstrapperParams) {
43 | super(bootstrapperParams);
44 | }
45 |
46 | public MetricCollectionPythonBoostrapper(int collectInterval) {
47 | this(BOOTSTRAPPER_ARG, generateCollectIntervalArgument(collectInterval));
48 | }
49 |
50 | // endregion Ctors
51 |
52 | // region Public methods
53 |
54 | @Override
55 | public MetricProvider getResult() {
56 | return new MetricProvider(new BufferedReader(new InputStreamReader(process.getInputStream())));
57 | }
58 |
59 | // endregion Public methods
60 |
61 | // region Private methods
62 |
63 | private static String generateCollectIntervalArgument(int collectInterval) {
64 | return String.format("--collect-interval=%s", String.valueOf(collectInterval));
65 | }
66 |
67 | // endregion Private methods
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/python/ProcessBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.python;
23 |
24 | import java.io.IOException;
25 |
26 | /**
27 | * Created by yonisha on 7/26/2015.
28 | */
29 | public interface ProcessBuilder {
30 | Process start() throws IOException;
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/python/PythonBootstrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.python;
23 |
24 | import java.io.IOException;
25 | import com.microsoft.applicationinsights.common.ArrayUtils;
26 |
27 | /**
28 | * Created by yonisha on 7/22/2015.
29 | */
30 | public abstract class PythonBootstrapper {
31 |
32 | // region Members
33 |
34 | private static final String PYTHON_BOOTSTRAP_SCRIPT = "python/bootstrap.py";
35 | protected ProcessBuilder processBuilder;
36 | protected Process process;
37 |
38 | // endregion Members
39 |
40 | // region Ctor
41 |
42 | protected PythonBootstrapper(ProcessBuilder processBuilder) {
43 | this.processBuilder = processBuilder;
44 | }
45 |
46 | public PythonBootstrapper(String... bootstrapperArgs) {
47 | String[] updatedParams = ArrayUtils.addFirst(PYTHON_BOOTSTRAP_SCRIPT, bootstrapperArgs);
48 |
49 | this.processBuilder = new PythonProcessBuilder(updatedParams);
50 | }
51 |
52 | // endregion Ctor
53 |
54 | // region Public
55 |
56 | public void start(boolean waitForExit) throws IOException {
57 | closePreviousProcessResources();
58 |
59 | try {
60 | this.process = processBuilder.start();
61 | if (waitForExit) {
62 | this.process.waitFor();
63 | }
64 | } catch (IOException e) {
65 | System.out.println(this.getClass().getSimpleName() + " failed to start python process with error: " + e.getMessage());
66 | } catch (InterruptedException e) {
67 | System.out.println(this.getClass().getSimpleName() + " has been interrupted. Error: " + e.getMessage());
68 | }
69 | }
70 |
71 | public boolean isAlive() {
72 | if (process == null) {
73 | return false;
74 | }
75 |
76 | return this.getExitValue() == -1;
77 | }
78 |
79 | public abstract T getResult();
80 |
81 | public int getExitValue() {
82 | if (this.process == null) {
83 | return -1;
84 | }
85 |
86 | try {
87 | return this.process.exitValue();
88 | } catch (java.lang.IllegalThreadStateException e) {
89 | return -1;
90 | }
91 | }
92 |
93 | public String getProcessExitInfo() {
94 | String bootstrapperClassName = this.getClass().getSimpleName();
95 |
96 | return "Python bootstrapper " + bootstrapperClassName + " has exited with exit code: " + this.getExitValue();
97 | }
98 |
99 | // endregion Public
100 |
101 | // region Private
102 |
103 | protected void closePreviousProcessResources() {
104 | if (process != null) {
105 | try {
106 | if (this.process.getInputStream() != null) {
107 | process.getInputStream().close();
108 | }
109 | if (this.process.getErrorStream() != null) {
110 | process.getErrorStream().close();
111 | }
112 | } catch (IOException e) {
113 | }
114 | }
115 | }
116 |
117 | // endregion Private
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/applicationinsights/python/PythonProcessBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.python;
23 |
24 | import com.microsoft.applicationinsights.common.ArrayUtils;
25 |
26 | import java.io.IOException;
27 |
28 | /**
29 | * Created by yonisha on 7/26/2015.
30 | */
31 | public class PythonProcessBuilder implements ProcessBuilder {
32 |
33 | private final java.lang.ProcessBuilder processBuilder;
34 | private final String PYTHON_EXE_NAME = "python";
35 |
36 | public PythonProcessBuilder(String... builderParams) {
37 | String[] updatedParams = ArrayUtils.addFirst(PYTHON_EXE_NAME, builderParams);
38 |
39 | this.processBuilder = new java.lang.ProcessBuilder(updatedParams);
40 | this.processBuilder.redirectError(java.lang.ProcessBuilder.Redirect.INHERIT);
41 | }
42 |
43 | public Process start() throws IOException {
44 | return processBuilder.start();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/com/microsoft/applicationinsights/AgentBootstrapperTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights;
23 |
24 | import com.microsoft.applicationinsights.common.ApplicationInsightsSender;
25 | import com.microsoft.applicationinsights.contracts.ContainerStateEvent;
26 | import com.microsoft.applicationinsights.contracts.ContainerStatsMetric;
27 | import com.microsoft.applicationinsights.python.ContainerStatePythonBootstrapper;
28 | import com.microsoft.applicationinsights.python.MetricCollectionPythonBoostrapper;
29 | import org.junit.Before;
30 | import org.junit.Test;
31 |
32 | import java.io.File;
33 | import java.io.IOException;
34 | import java.io.InputStream;
35 | import java.nio.file.Files;
36 | import java.nio.file.StandardCopyOption;
37 |
38 | import static org.mockito.Matchers.any;
39 | import static org.mockito.Mockito.mock;
40 | import static org.mockito.Mockito.times;
41 | import static org.mockito.Mockito.verify;
42 |
43 | /**
44 | * Created by yonisha on 8/3/2015.
45 | */
46 | public class AgentBootstrapperTests {
47 | private static final String PYTHON_TEST_METRIC_EVENT_SCRIPT_FILENAME = "test_metric_event.py";
48 | private static final String PYTHON_TEST_STATE_EVENT_SCRIPT_FILENAME = "test_state_event.py";
49 | private AgentBootstrapper agentBootstrapperUnderTest = new AgentBootstrapper();
50 | private ApplicationInsightsSender aiSenderMock;
51 |
52 | @Before
53 | public void initTest() {
54 | aiSenderMock = mock(ApplicationInsightsSender.class);
55 | }
56 |
57 | @Test
58 | public void testMetricCollectionProcess() throws InterruptedException, IOException {
59 | String testPythonScriptPath = getTestPythonScriptPath(PYTHON_TEST_METRIC_EVENT_SCRIPT_FILENAME);
60 |
61 | MetricCollectionPythonBoostrapper metricCollectionPythonBoostrapper = new MetricCollectionPythonBoostrapper("custom", "--script", testPythonScriptPath);
62 | Thread thread = AgentBootstrapper.createMetricCollectionProcess(aiSenderMock, metricCollectionPythonBoostrapper);
63 |
64 | agentBootstrapperUnderTest.run(aiSenderMock, thread, createWorklessThread(), createWorklessThread());
65 |
66 | verify(aiSenderMock, times(1)).track(any(ContainerStatsMetric.class));
67 | }
68 |
69 | @Test
70 | public void testContainerStateProcess() throws InterruptedException, IOException {
71 | String testPythonScriptPath = getTestPythonScriptPath(PYTHON_TEST_STATE_EVENT_SCRIPT_FILENAME);
72 |
73 | ContainerStatePythonBootstrapper bootstrapper = new ContainerStatePythonBootstrapper("custom", "--script", testPythonScriptPath);
74 | Thread thread = AgentBootstrapper.createContainerStateProcess(aiSenderMock, bootstrapper);
75 |
76 | agentBootstrapperUnderTest.run(aiSenderMock, createWorklessThread(), thread, createWorklessThread());
77 |
78 | verify(aiSenderMock, times(1)).track(any(ContainerStateEvent.class));
79 | }
80 |
81 | private String getTestPythonScriptPath(String filename) throws IOException {
82 | ClassLoader classLoader = AgentBootstrapperTests.class.getClassLoader();
83 | InputStream resourceAsStream = classLoader.getResourceAsStream(filename);
84 |
85 | // ProcessBuilder cannot handle path containing spaces. Therefore, we copy the test python script into
86 | // a temp location and loading it from there.
87 | String tempDir = System.getProperty("java.io.tmpdir");
88 | File tempPython = new File(tempDir, filename);
89 | Files.copy(resourceAsStream, tempPython.toPath(), StandardCopyOption.REPLACE_EXISTING);
90 | System.out.println("Test python script has been copied to : " + tempPython);
91 |
92 | return tempPython.getAbsolutePath();
93 | }
94 |
95 | private Thread createWorklessThread() {
96 | return new Thread(new Runnable() {
97 | @Override
98 | public void run() {
99 | }
100 | });
101 | }
102 | }
--------------------------------------------------------------------------------
/src/test/java/com/microsoft/applicationinsights/agent/DockerAgentTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ApplicationInsights-Docker
3 | * Copyright (c) Microsoft Corporation
4 | * All rights reserved.
5 | *
6 | * MIT License
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
8 | * software and associated documentation files (the ""Software""), to deal in the Software
9 | * without restriction, including without limitation the rights to use, copy, modify, merge,
10 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11 | * persons to whom the Software is furnished to do so, subject to the following conditions:
12 | * The above copyright notice and this permission notice shall be included in all copies or
13 | * substantial portions of the Software.
14 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | * DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package com.microsoft.applicationinsights.agent;
23 |
24 | import com.microsoft.applicationinsights.common.ApplicationInsightsSender;
25 | import com.microsoft.applicationinsights.common.TestConstants;
26 | import com.microsoft.applicationinsights.contracts.ContainerStatsMetric;
27 | import com.microsoft.applicationinsights.providers.MetricProvider;
28 | import com.microsoft.applicationinsights.python.PythonBootstrapper;
29 | import org.junit.Before;
30 | import org.junit.Test;
31 | import org.mockito.invocation.InvocationOnMock;
32 | import org.mockito.stubbing.Answer;
33 |
34 | import java.io.IOException;
35 |
36 | import static org.mockito.Mockito.*;
37 |
38 | /**
39 | * Created by yonisha on 7/26/2015.
40 | */
41 | public class DockerAgentTests {
42 | private ApplicationInsightsSender applicationInsightsSender;
43 | private PythonBootstrapper pythonBootstrapper;
44 | private DockerAgent agentUnderTest;
45 | private MetricProvider metricProviderMock;
46 |
47 | @Before
48 | public void testInit() throws IOException {
49 | initializeMocks();
50 |
51 | agentUnderTest.run();
52 | }
53 |
54 | @Test
55 | public void testWhenAgentStartPythonBoostrapperStarts() throws IOException {
56 | verify(pythonBootstrapper, atLeastOnce()).start(false);
57 | }
58 |
59 | @Test
60 | public void testWhenAgentStartsMetricsBeingCollected() throws IOException {
61 | verify(metricProviderMock, atLeastOnce()).getNext();
62 | }
63 |
64 | @Test
65 | public void testMetricIsSent() {
66 | verify(this.applicationInsightsSender, atLeastOnce()).track(any(ContainerStatsMetric.class));
67 | }
68 |
69 | @Test
70 | public void testOnlyOnePythonProcessStarts() throws IOException {
71 | verify(this.pythonBootstrapper, times(1)).start(false);
72 | }
73 |
74 | @Test
75 | public void testWhenMetricProviderReturnsNullAndPythonProcessAliveNoNewPythonProcessIsStarted() throws IOException {
76 | initializeMocks();
77 |
78 | final int[] numOfTrackedEvents = new int[1];
79 | when(this.metricProviderMock.getNext()).thenAnswer(new Answer