This repository has been archived by the owner on Oct 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Example : Web Client Monitoring (#218)
* Initial checkin * Removed @license tags, .gitignore files, and package-lock.json, as per review comments * Removed unnecessary test line and fixed comment * Fixed typos, added schematic diagram. * Change to make effective use of tags * Changed copyright statement * Added additional tag for web client * Fixed typo in README * Fixed typo in README * Fixed image links * Fixed second image link * Added client tag key to view * Added tag client to clickCountView * Fixed broken image links * Fixed broken images * Fixed second image * Cropped schematic diagram
- Loading branch information
1 parent
42338a2
commit 6391818
Showing
11 changed files
with
382 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Monitoring Web Metrics with OpenCensus and Stackdriver | ||
This example demonstrates instrumentation of browser code in a web application | ||
for monitoring web metrics with a JavaScript module. See the schematic diagram | ||
below for a graphical description. | ||
|
||
![Schematic Diagram](https://user-images.githubusercontent.com/5554156/49771515-4b1ca180-fc9e-11e8-8c2a-07877ee096a3.png) | ||
|
||
## Prerequisites | ||
Install [Node.js](https://nodejs.org) in your local development environment. | ||
Create a Google Cloud Platform (GCP) project and set it as your default project | ||
with the command | ||
``` | ||
export GOOGLE_CLOUD_PROJECT=[your project id] | ||
gcloud config set project $GOOGLE_CLOUD_PROJECT | ||
``` | ||
|
||
You will need the environment variable GOOGLE_CLOUD_PROJECT exported to the | ||
Node.js runtime for it to send the data to Stackdriver when running in a local | ||
development environment. The choice of variable name matches the [App Engine | ||
Flex](https://cloud.google.com/appengine/docs/standard/nodejs/runtime#environment_variables) | ||
environment variable name. | ||
|
||
Enable the [Stackdriver API](https://cloud.google.com/monitoring/api/v3/) with | ||
the command | ||
``` | ||
gcloud services enable monitoring | ||
``` | ||
|
||
You can run the example with another monitoring backend if you modify the code | ||
in app.js to use a different exporter. | ||
|
||
## Web Client Compilation | ||
Set up Nodejs and Webpack: | ||
``` | ||
cd web_client | ||
npm install | ||
``` | ||
|
||
Compile the client JavaScript | ||
``` | ||
npm run build | ||
``` | ||
|
||
## Running locally | ||
To run the example locally follow the instructions in [Getting Started with | ||
Authentication](https://cloud.google.com/docs/authentication/getting-started) | ||
to make a service account key available via the GOOGLE_APPLICATION_CREDENTIALS | ||
environment variable. Then serve the HTML file from the Node.js server with | ||
Express: | ||
``` | ||
cd | ||
npm install | ||
npm start | ||
``` | ||
|
||
Navigate to the page at http://localhost:8080 with a browser. | ||
|
||
## Deploying to App Engine | ||
The app can be deployed to App Engine with no changes. All that is needed is | ||
an app.yaml file. To deploy it run the command | ||
``` | ||
npm run deploy | ||
``` | ||
|
||
This will use the gcloud app deploy command, which will deploy the app as an | ||
Express app using the App Engine Flex Nodejs runtime. | ||
|
||
## Configuring Stackdriver | ||
To see the metrics data in Stackdriver, select Monitoring in the main menu to | ||
bring up the Stackdriver user interface. Create a new dashboard. Create new | ||
charts for the dashboard. Add the metrics collected to the chart. The metric | ||
names are listed in the app.js file. An example screenshot is shown below. | ||
You may want to use one chart for the (double value) timing data and one chart | ||
for the (integer value) click counts. | ||
|
||
![Stackdriver Dashboard](https://user-images.githubusercontent.com/4898263/49771039-190a4000-fc9c-11e8-9536-9ce4172606b8.png) | ||
|
||
## Troubleshooting | ||
If your monitoring data does not show up in Stackdriver, check the GCP | ||
[API Dashboard](https://cloud.google.com/apis/docs/monitoring) for the Cloud | ||
Monitoring API and the [App Engine | ||
logs](https://cloud.google.com/appengine/articles/logging) for errors in the | ||
Google Cloud Console and Developer tools in the browser. See [Troubleshooting | ||
the Monitoring API](https://cloud.google.com/monitoring/api/troubleshooting) | ||
for more tips. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/** | ||
* Copyright 2018, OpenCensus Authors | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/** | ||
* An Express application to receive the web metrics and send to Stackdriver | ||
* with the opencensus API. | ||
*/ | ||
|
||
"use strict"; | ||
|
||
const express = require("express"); | ||
const assert = require('assert'); | ||
const process = require("process"); | ||
const bodyParser = require('body-parser'); | ||
// [START web_client_monitoring_imports] | ||
const { Stats, MeasureUnit, AggregationType } = require('@opencensus/core'); | ||
const { StackdriverStatsExporter } = require('@opencensus/exporter-stackdriver'); | ||
// [END web_client_monitoring_imports] | ||
|
||
const app = express(); | ||
app.use(express.static("web_client")); | ||
app.use(bodyParser.json()); | ||
|
||
// The project id must be provided for running in a local environment | ||
const project = process.env.GOOGLE_CLOUD_PROJECT; | ||
assert(typeof project !== 'undefined' && project, | ||
"Please set environment variable GOOGLE_CLOUD_PROJECT to the project id."); | ||
console.log(`Sending metrics data to project: ${project}`); | ||
|
||
// OpenCensus setup | ||
// [START web_client_monitoring_ocsetup] | ||
const stats = new Stats(); | ||
const exporter = new StackdriverStatsExporter({projectId: project}); | ||
stats.registerExporter(exporter); | ||
const mLatencyMs = stats.createMeasureDouble("webmetrics/latency", | ||
MeasureUnit.MS, | ||
"Latency related to page loading"); | ||
const mClickCount = stats.createMeasureInt64("webmetrics/click_count", | ||
MeasureUnit.UNIT, | ||
"Number of clicks"); | ||
const buckets = [0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, | ||
100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, | ||
5000, 10000, 20000, 50000, 100000]; | ||
const tagPhase = "phase"; | ||
const tagClient = "client"; | ||
const latencyView = stats.createView( | ||
"webmetrics/latency", | ||
mLatencyMs, | ||
AggregationType.DISTRIBUTION, | ||
[tagPhase, tagClient], | ||
"Distribution of latencies", | ||
buckets | ||
); | ||
const clickCountView = stats.createView( | ||
"webmetrics/click_count", | ||
mClickCount, | ||
AggregationType.COUNT, | ||
[tagClient], | ||
"The number of button clicks" | ||
); | ||
// [END web_client_monitoring_ocsetup] | ||
|
||
// Process the metrics data posted to the server | ||
app.post("/metrics", (req, res) => { | ||
const dnsTime = req.body["dnsTime"]; | ||
const connectTime = req.body["connectTime"]; | ||
const totalTime = req.body["totalTime"]; | ||
const clickCount = req.body["count"]; | ||
console.log(`totalTime ${totalTime}`); | ||
console.log(`connectTime ${connectTime}`); | ||
console.log(`dnsTime ${dnsTime}`); | ||
console.log(`count ${clickCount}`); | ||
const valueTLSNegotiation = "tls_negotiation"; | ||
const valueDNSLookup = "dns_lookup"; | ||
const valueLoad = "load"; | ||
const valueWeb = "web"; | ||
let tags = { phase: valueDNSLookup, client: valueWeb }; | ||
// [START web_client_monitoring_record] | ||
try { | ||
stats.record({ | ||
measure: mLatencyMs, | ||
tags, | ||
value: dnsTime | ||
}); | ||
tags = { phase: valueTLSNegotiation, client: valueWeb }; | ||
stats.record({ | ||
measure: mLatencyMs, | ||
tags, | ||
value: connectTime | ||
}); | ||
tags = { phase: valueLoad, client: valueWeb }; | ||
stats.record({ | ||
measure: mLatencyMs, | ||
tags, | ||
value: totalTime | ||
}); | ||
tags = { client: valueWeb }; | ||
stats.record({ | ||
measure: mClickCount, | ||
tags, | ||
value: clickCount | ||
}); | ||
res.status(200).send("Received").end(); | ||
console.log('Competed recording metrics'); | ||
} catch (err) { | ||
console.log(`Could not save stats: ${err}`); | ||
res.status(500).send("Error saving stats").end(); | ||
} | ||
// [END web_client_monitoring_record] | ||
}); | ||
|
||
// Start the server | ||
const PORT = process.env.PORT || 8080; | ||
app.listen(PORT, () => { | ||
console.log(`Listening on port ${PORT}`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
runtime: nodejs | ||
env: flex | ||
|
||
manual_scaling: | ||
instances: 1 | ||
resources: | ||
cpu: 1 | ||
memory_gb: 0.5 | ||
disk_size_gb: 10 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+158 KB
examples/stats/web_client_monitoring/drawings/stackdriver_dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "web-client-monitoring", | ||
"description": "Example of saving web metrics", | ||
"version": "0.0.1", | ||
"private": true, | ||
"license": "Apache-2.0", | ||
"author": "Google Inc.", | ||
"engines": { | ||
"node": ">=4.3.2" | ||
}, | ||
"scripts": { | ||
"deploy": "gcloud app deploy", | ||
"start": "node app.js", | ||
"lint": "npm run lint", | ||
}, | ||
"dependencies": { | ||
"@opencensus/core": "0.0.7", | ||
"@opencensus/exporter-stackdriver": "0.0.7", | ||
"express": "^4.16.3" | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
examples/stats/web_client_monitoring/web_client/client_app.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* Copyright 2018, OpenCensus Authors | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
const WebMetrics = require("./metricsclient.js"); | ||
new WebMetrics("#incrementbutton", "#flushbutton"); |
18 changes: 18 additions & 0 deletions
18
examples/stats/web_client_monitoring/web_client/index.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>Web Metrics Monitoring Example</title> | ||
<link rel="icon" href="data:,"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
</head> | ||
<body> | ||
<section> | ||
<h1>Web Metrics Monitoring Example</h1> | ||
<p>A page to demonstrate monitoring of web metrics.</p> | ||
<div>Click me: <button id="incrementbutton" type="button">Increment</button></div> | ||
<div>Save me: <button id="flushbutton" type="button">Flush</button></div> | ||
</section> | ||
<script src="dist/app_bundle.js"></script> | ||
</body> | ||
</html> |
81 changes: 81 additions & 0 deletions
81
examples/stats/web_client_monitoring/web_client/metricsclient.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Copyright 2018, OpenCensus Authors | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/** | ||
* A Common JS module for proxying monitoring and trace calls to the server | ||
*/ | ||
|
||
/** | ||
* A class to collect web metrics and send them to the server | ||
*/ | ||
class WebMetrics { | ||
|
||
/** | ||
* Creates a WebMetrics instance | ||
* @param {string} incrementbutton - ID of the increment button DOM element | ||
* @param {string} flushbutton - ID of the flush button DOM element | ||
*/ | ||
constructor(incrementbutton, flushbutton) { | ||
// [START web_client_monitoring_click_counter] | ||
this.click_counter_ = 0; | ||
// [END web_client_monitoring_click_counter] | ||
const wm = this; | ||
const incrementbtn = document.querySelector(incrementbutton); | ||
incrementbtn.onclick = function() { | ||
wm.click_counter_++; | ||
} | ||
const flushbtn = document.querySelector(flushbutton); | ||
flushbtn.onclick = function() { | ||
wm.post_data_(wm.click_counter_); | ||
} | ||
} | ||
|
||
/** | ||
* Send the metrics data to the server | ||
* @private | ||
*/ | ||
post_data_(count) { | ||
// [START web_client_monitoring_performance] | ||
const pageNav = performance.getEntriesByType("navigation")[0]; | ||
const dnsTime = pageNav.domainLookupEnd - pageNav.domainLookupStart; | ||
const connectTime = pageNav.connectEnd - pageNav.connectStart; | ||
const ttfb = pageNav.responseStart - pageNav.requestStart; | ||
const totalTime = pageNav.responseEnd - pageNav.requestStart; | ||
// [END web_client_monitoring_performance] | ||
const data = { | ||
dnsTime: dnsTime, | ||
connectTime: connectTime, | ||
ttfb: ttfb, | ||
totalTime: totalTime, | ||
count: count | ||
} | ||
fetch("/metrics", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json; charset=utf-8", | ||
}, | ||
body: JSON.stringify(data), // body data type must match "Content-Type" header | ||
}) | ||
.then(function(response) { | ||
if(response.ok) { | ||
console.log("Data received"); | ||
} else { | ||
console.log("Error sending data"); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
module.exports = WebMetrics |
17 changes: 17 additions & 0 deletions
17
examples/stats/web_client_monitoring/web_client/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "web_monitoring_client", | ||
"version": "0.0.1", | ||
"description": "Web client for monitoring example", | ||
"main": "client_app.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"build": "webpack --mode production" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"webpack": "^4.25.1", | ||
"webpack-cli": "^3.1.2" | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
examples/stats/web_client_monitoring/web_client/webpack.config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
entry: './client_app.js', | ||
output: { | ||
filename: 'app_bundle.js', | ||
} | ||
} |