Skip to content
This repository has been archived by the owner on Oct 3, 2023. It is now read-only.

Commit

Permalink
Example : Web Client Monitoring (#218)
Browse files Browse the repository at this point in the history
* 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
alexpamies authored and mayurkale22 committed Dec 11, 2018
1 parent 42338a2 commit 6391818
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 0 deletions.
85 changes: 85 additions & 0 deletions examples/stats/web_client_monitoring/README.md
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.
128 changes: 128 additions & 0 deletions examples/stats/web_client_monitoring/app.js
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}`);
});
9 changes: 9 additions & 0 deletions examples/stats/web_client_monitoring/app.yaml
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions examples/stats/web_client_monitoring/package.json
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 examples/stats/web_client_monitoring/web_client/client_app.js
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 examples/stats/web_client_monitoring/web_client/index.html
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 examples/stats/web_client_monitoring/web_client/metricsclient.js
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 examples/stats/web_client_monitoring/web_client/package.json
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"
}
}
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',
}
}

0 comments on commit 6391818

Please sign in to comment.