Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
varnishapi
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
uplex-varnish
varnishapi
Commits
5e4091f1
Commit
5e4091f1
authored
Aug 18, 2018
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
govarnishstat runs a websockets app to imitate varnishstat curses mode.
parent
c6389ce6
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
709 additions
and
0 deletions
+709
-0
govarnishstat.go
cmd/govarnishstat/govarnishstat.go
+2
-0
table.go
cmd/govarnishstat/table.go
+381
-0
websocks.go
cmd/govarnishstat/websocks.go
+326
-0
No files found.
cmd/govarnishstat/govarnishstat.go
View file @
5e4091f1
...
@@ -274,4 +274,6 @@ func main() {
...
@@ -274,4 +274,6 @@ func main() {
if
*
xmlf
{
if
*
xmlf
{
doXML
(
vstats
)
doXML
(
vstats
)
}
}
doWebSocks
(
vstats
)
}
}
cmd/govarnishstat/table.go
0 → 100644
View file @
5e4091f1
/*-
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package
main
const
table
=
`<!DOCTYPE html>
<html>
<head>
<title>govarnishstat</title>
<style>
table {
border-collapse: collapse;
font-family: monospace,monospace;
}
table, td, th {
border: 2px solid black;
}
td.name, th.name {
width: 24em;
text-align: left;
}
td, th {
width: 14em;
text-align: right;
padding: 0.25rem;
}
th {
background-color: black;
color: white;
}
tr:hover {background-color: WhiteSmoke;}
tr.hidden { display: none; }
table.uptime {
float: left;
text-align: left;
width: 18em;
}
table.hitrate {
float: right;
width: 23em;
}
.top {
border: none;
}
#stats {
clear: both;
}
#wrapper {
display: inline-block;
}
.controls {
font-family: monospace,monospace;
margin: 0 10px;
float: right;
}
</style>
<script type="text/javascript">
var addr = "ws://" + location.hostname + ":" + location.port
var tbl
var tbody
var show0 = false
var formatting = true
var verbosity = "INFO"
var verbose_select
var diag
var debug
function format_num(cell, val) {
if (cell.classList.contains("float")) {
cell.innerHTML = val.toFixed(2) + " "
return
}
cell.innerHTML = val + " "
}
function format_duration(cell, val) {
if (cell.classList.contains("float")) {
return
}
var days = Math.floor(val/86400).toFixed(0)
var hours = (Math.floor((val % 86400)/3600)).toFixed(0)
if (hours < 10) {
hours = "0" + hours
}
var mins = (Math.floor((val % 3600)/60)).toFixed(0)
if (mins < 10) {
mins = "0" + mins
}
secs = (val % 60).toFixed(0)
if (secs < 10) {
secs = "0" + secs
}
cell.innerHTML = days + "+" + hours + ":" + mins + ":" + secs + " "
}
function format_bytes(cell, val) {
var suffix = [" ", "K", "M", "G", "T", "P", "E", "Z", "Y"]
var i
if (cell.classList.contains("int") && val < 1024) {
cell.innerHTML = val.toFixed(0) + " "
return
}
for (i = 0; val >= 1024; i++)
val /= 1024
cell.innerHTML = val.toFixed(2) + suffix[i]
}
function format_bitmap(cell, val, bitmap) {
if (!cell.classList.contains("int")) {
return
}
if (!formatting) {
format_num(cell, val)
return
}
cell.innerHTML = bitmap + " "
}
function format(cell, val) {
if (!formatting) {
format_num(cell, val)
return
}
if (cell.classList.contains("d")) {
format_duration(cell, val)
return
}
if (cell.classList.contains("B")) {
format_bytes(cell, val)
return
}
if (cell.classList.contains("b")) {
return
}
format_num(cell, val)
}
function getD9ns() {
var ws = new WebSocket(addr + "/d9ns")
ws.onmessage = function (evt) {
var d9ns = JSON.parse(evt.data)
for (i = 0; i < d9ns.length; i++) {
var row = tbody.insertRow(i)
var nameCell = row.insertCell(0)
var curCell = row.insertCell(1)
var changeCell = row.insertCell(2)
var avgCell = row.insertCell(3)
var avg10Cell = row.insertCell(4)
var avg100Cell = row.insertCell(5)
var avg1000Cell = row.insertCell(6)
curCell.classList.add("int", d9ns[i].format,
d9ns[i].semantics)
changeCell.classList.add("float", d9ns[i].format,
d9ns[i].semantics)
avgCell.classList.add("float", d9ns[i].format,
d9ns[i].semantics)
avg10Cell.classList.add("float", d9ns[i].format,
d9ns[i].semantics)
avg100Cell.classList.add("float", d9ns[i].format,
d9ns[i].semantics)
avg1000Cell.classList.add("float", d9ns[i].format,
d9ns[i].semantics)
if (d9ns[i].semantics == "g") {
avgCell.innerHTML = ". "
}
row.id = d9ns[i].name
nameCell.innerHTML = d9ns[i].name
nameCell.className = "name"
row.id = d9ns[i].name
row.classList.add(d9ns[i].level)
if (d9ns[i].level != "INFO") {
row.classList.add("hidden")
}
}
};
}
function getStats() {
var ws = new WebSocket(addr + "/stats")
ws.onmessage = function (evt) {
var stats = JSON.parse(evt.data)
var row = document.getElementById(stats.name)
var cells = row.cells
if (cells[1].classList.contains("b")) {
format_bitmap(cells[1], stats.value, stats.bitmap)
}
else {
format(cells[1], stats.value)
}
format(cells[2], stats.change)
if (!cells[3].classList.contains("g")) {
format(cells[3], stats.avg)
}
format(cells[4], stats.avg10)
format(cells[5], stats.avg100)
format(cells[6], stats.avg1000)
if (!show0 && stats.value == 0) {
row.classList.add("hidden")
}
if (stats.name == "MGT.uptime") {
var cell = document.getElementById("uptime_mgt")
format_duration(cell, stats.value)
}
if (stats.name == "MAIN.uptime") {
var cell = document.getElementById("uptime_chld")
format_duration(cell, stats.value)
}
ws.send("ACK");
};
}
function format_hitrate(id, val) {
var cell = document.getElementById(id)
cell.innerHTML = val.toFixed(4)
}
function getHitrate() {
var ws = new WebSocket(addr + "/hitrate")
ws.onmessage = function (evt) {
var stats = JSON.parse(evt.data)
format_hitrate("hitrate10", stats.avg10)
format_hitrate("hitrate100", stats.avg100)
format_hitrate("hitrate1000", stats.avg1000)
ws.send("ACK");
};
}
function setHidden(rows) {
for (i = 0; i < rows.length; i++) {
rows[i].classList.add("hidden")
}
}
function setVisible(rows) {
for (i = 0; i < rows.length; i++) {
rows[i].classList.remove("hidden")
}
}
function setVerbosity() {
var verbosity = verbose_select.value
if (verbosity == "INFO") {
setHidden(diag)
setHidden(debug)
return
}
if (verbosity == "DIAG") {
setVisible(diag)
setHidden(debug)
return
}
setVisible(diag)
setVisible(debug)
}
window.onload = function() {
tbl = document.getElementById("statsTbl")
tbody = tbl.getElementsByTagName('tbody')[0];
getD9ns()
getStats()
getHitrate()
verbose_select = document.getElementById("verbosity")
verbose_select.addEventListener("change", setVerbosity);
diag = document.getElementsByClassName("DIAG")
debug = document.getElementsByClassName("DEBUG")
}
</script>
</head>
<body>
<div id="wrapper">
<div id="header">
<table class="uptime top">
<tr class="top">
<td class="top">Uptime mgt:</td>
<td class="top" id="uptime_mgt"></td>
</tr>
<tr class="top">
<td class="top">Uptime child:</td>
<td class="top" id="uptime_chld"></td>
</tr>
</table>
<table class="hitrate top">
<tr>
<td class="top">Hitrate n:</td>
<td class="top">10</td>
<td class="top">100</td>
<td class="top">1000</td>
</tr>
<tr class="top">
<td class="top">avg(n):</td>
<td class="top" id="hitrate10"></td>
<td class="top" id="hitrate100"></td>
<td class="top" id="hitrate1000"></td>
</tr>
</table>
</div>
<div id="stats">
<br />
<table id="statsTbl">
<thead>
<tr>
<th class="name">NAME</th>
<th>CURRENT </th>
<th>CHANGE </th>
<th>AVERAGE </th>
<th>AVG_10 </th>
<th>AVG_100 </th>
<th>AVG_1000 </th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="controls">
<div class="controls">
<p>Verbosity</p>
<select id="verbosity">
<option value="INFO">INFO</option>
<option value="DIAG">DIAG</option>
<option value="DEBUG">DEBUG</option>
</select>
</div>
<div class="controls">
<p>Current=0</p>
<select id="zero">
<option value="false">Hide</option>
<option value="true">Show</option>
</select>
</div>
</div>
</div>
</body>
</html>`
cmd/govarnishstat/websocks.go
0 → 100644
View file @
5e4091f1
/*-
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package
main
import
(
"golang.org/x/net/websocket"
"uplex.de/varnishapi/pkg/stats"
"errors"
"fmt"
"net"
"net/http"
"os/exec"
"runtime"
"time"
)
var
vstats
*
stats
.
Stats
func
tableHandler
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
w
.
Write
([]
byte
(
table
))
}
type
jsonD9n
struct
{
Name
string
`json:"name"`
Short
string
`json:"short"`
Long
string
`json:"long"`
Level
string
`json:"level"`
Format
string
`json:"format"`
S7s
string
`json:"semantics"`
}
func
d9nsHandler
(
ws
*
websocket
.
Conn
)
{
names
,
err
:=
vstats
.
Names
()
if
err
!=
nil
{
errExit
(
err
)
}
var
data
[]
jsonD9n
for
_
,
name
:=
range
names
{
d9n
,
err
:=
vstats
.
D9n
(
name
)
if
err
!=
nil
{
errExit
(
err
)
}
jd9n
:=
jsonD9n
{}
jd9n
.
Name
=
name
jd9n
.
Short
=
d9n
.
ShortD9n
jd9n
.
Long
=
d9n
.
LongD9n
jd9n
.
Level
=
d9n
.
Level
.
Label
jd9n
.
Format
=
d9n
.
Format
.
String
()
jd9n
.
S7s
=
d9n
.
Semantics
.
String
()
data
=
append
(
data
,
jd9n
)
}
websocket
.
JSON
.
Send
(
ws
,
data
)
}
type
movAvg
struct
{
n
uint
max
uint
acc
float64
}
func
(
avg
*
movAvg
)
update
(
val
float64
)
{
if
avg
.
n
<
avg
.
max
{
avg
.
n
++
}
avg
.
acc
+=
(
val
-
avg
.
acc
)
/
float64
(
avg
.
n
)
}
type
state
struct
{
last
uint64
tlast
time
.
Time
avg10
movAvg
avg100
movAvg
avg1000
movAvg
s7s
stats
.
Semantics
}
var
statState
map
[
string
]
*
state
type
jStats
struct
{
Name
string
`json:"name"`
Value
uint64
`json:"value"`
Change
float64
`json:"change"`
Avg
float64
`json:"avg"`
Avg10
float64
`json:"avg10"`
Avg100
float64
`json:"avg100"`
Avg1000
float64
`json:"avg1000"`
Bitmap
string
`json:"bitmap"`
}
func
initCB
(
name
string
,
val
uint64
)
bool
{
newState
:=
state
{}
newState
.
tlast
=
time
.
Now
()
newState
.
last
=
val
d9n
,
err
:=
vstats
.
D9n
(
name
)
if
err
!=
nil
{
errExit
(
err
)
}
s7s
:=
d9n
.
Semantics
newState
.
s7s
=
s7s
if
s7s
==
stats
.
Counter
||
s7s
==
stats
.
Gauge
{
avg10
:=
movAvg
{
0
,
10
,
0
}
newState
.
avg10
=
avg10
avg100
:=
movAvg
{
0
,
100
,
0
}
newState
.
avg100
=
avg100
avg1000
:=
movAvg
{
0
,
1000
,
0
}
newState
.
avg1000
=
avg1000
}
statState
[
name
]
=
&
newState
return
true
}
func
statsHandler
(
ws
*
websocket
.
Conn
)
{
var
uptime
uint64
uptimeCB
:=
func
(
name
string
,
val
uint64
)
bool
{
if
name
==
"MAIN.uptime"
{
uptime
=
val
return
false
}
return
true
}
readCB
:=
func
(
name
string
,
val
uint64
)
bool
{
now
:=
time
.
Now
()
var
jstats
jStats
state
,
ok
:=
statState
[
name
]
if
!
ok
{
errExit
(
errors
.
New
(
"uninitialized stat: "
+
name
))
}
jstats
.
Name
=
name
jstats
.
Value
=
val
dsecs
:=
now
.
Sub
(
state
.
tlast
)
.
Seconds
()
change
:=
float64
(
val
-
state
.
last
)
/
dsecs
state
.
last
=
val
state
.
tlast
=
now
jstats
.
Change
=
change
if
state
.
s7s
==
stats
.
Gauge
{
state
.
avg10
.
update
(
float64
(
val
))
state
.
avg100
.
update
(
float64
(
val
))
state
.
avg1000
.
update
(
float64
(
val
))
jstats
.
Avg10
=
state
.
avg10
.
acc
jstats
.
Avg100
=
state
.
avg100
.
acc
jstats
.
Avg1000
=
state
.
avg1000
.
acc
}
else
if
state
.
s7s
==
stats
.
Counter
{
jstats
.
Avg
=
float64
(
val
)
/
float64
(
uptime
)
state
.
avg10
.
update
(
change
)
state
.
avg100
.
update
(
change
)
state
.
avg1000
.
update
(
change
)
jstats
.
Avg10
=
state
.
avg10
.
acc
jstats
.
Avg100
=
state
.
avg100
.
acc
jstats
.
Avg1000
=
state
.
avg1000
.
acc
}
else
if
state
.
s7s
==
stats
.
S7sBitmap
{
jstats
.
Bitmap
=
fmt
.
Sprintf
(
"%10.10x"
,
((
val
>>
24
)
&
0xffffffffff
))
}
websocket
.
JSON
.
Send
(
ws
,
jstats
)
return
true
}
for
{
err
:=
vstats
.
Read
(
uptimeCB
)
if
err
!=
nil
{
errExit
(
err
)
}
err
=
vstats
.
Read
(
readCB
)
if
err
!=
nil
{
errExit
(
err
)
}
time
.
Sleep
(
time
.
Second
)
}
}
var
lsnr
net
.
Listener
func
ackListener
(
ws
*
websocket
.
Conn
)
{
for
{
var
msg
[]
byte
websocket
.
Message
.
Receive
(
ws
,
&
msg
)
if
len
(
msg
)
==
0
{
fmt
.
Println
(
"Client closed the connection"
)
lsnr
.
Close
()
return
}
}
}
type
hrStats
struct
{
Avg10
float64
`json:"avg10"`
Avg100
float64
`json:"avg100"`
Avg1000
float64
`json:"avg1000"`
}
func
hitrateHandler
(
ws
*
websocket
.
Conn
)
{
var
hits
uint64
var
misses
uint64
var
lastHits
uint64
var
lastMisses
uint64
var
gotHits
bool
var
gotMisses
bool
hr10
:=
movAvg
{
0
,
10
,
0
}
hr100
:=
movAvg
{
0
,
100
,
0
}
hr1000
:=
movAvg
{
0
,
1000
,
0
}
hrCB
:=
func
(
name
string
,
val
uint64
)
bool
{
if
name
==
"MAIN.cache_hit"
{
hits
=
val
gotHits
=
true
}
else
if
name
==
"MAIN.cache_miss"
{
misses
=
val
gotMisses
=
true
}
return
!
(
gotHits
&&
gotMisses
)
}
go
ackListener
(
ws
)
for
{
var
hr
float64
var
dhits
float64
var
dmisses
float64
gotHits
=
false
gotMisses
=
false
if
err
:=
vstats
.
Read
(
hrCB
);
err
!=
nil
{
errExit
(
err
)
}
dhits
=
float64
(
hits
-
lastHits
)
dmisses
=
float64
(
misses
-
lastMisses
)
lastHits
=
hits
lastMisses
=
misses
if
dhits
+
dmisses
!=
0
{
hr
=
dhits
/
(
dhits
+
dmisses
)
}
else
{
hr
=
0
}
hr10
.
update
(
hr
)
hr100
.
update
(
hr
)
hr1000
.
update
(
hr
)
hrstats
:=
hrStats
{}
hrstats
.
Avg10
=
hr10
.
acc
hrstats
.
Avg100
=
hr100
.
acc
hrstats
.
Avg1000
=
hr1000
.
acc
websocket
.
JSON
.
Send
(
ws
,
hrstats
)
time
.
Sleep
(
time
.
Second
)
}
}
func
openbrowser
(
url
string
)
{
switch
runtime
.
GOOS
{
case
"freebsd"
:
case
"linux"
:
if
err
:=
exec
.
Command
(
"xdg-open"
,
url
)
.
Start
();
err
!=
nil
{
errExit
(
err
)
}
case
"darwin"
:
if
err
:=
exec
.
Command
(
"open"
,
url
)
.
Start
();
err
!=
nil
{
errExit
(
err
)
}
}
}
func
doWebSocks
(
s
*
stats
.
Stats
)
{
vstats
=
s
names
,
err
:=
s
.
Names
()
if
err
!=
nil
{
errExit
(
err
)
}
len
:=
len
(
names
)
statState
=
make
(
map
[
string
]
*
state
,
len
)
if
err
:=
s
.
Read
(
initCB
);
err
!=
nil
{
errExit
(
err
)
}
listener
,
err
:=
net
.
Listen
(
"tcp"
,
"localhost:0"
)
if
err
!=
nil
{
errExit
(
err
)
}
lsnr
=
listener
http
.
HandleFunc
(
"/"
,
tableHandler
)
http
.
Handle
(
"/d9ns"
,
websocket
.
Handler
(
d9nsHandler
))
http
.
Handle
(
"/stats"
,
websocket
.
Handler
(
statsHandler
))
http
.
Handle
(
"/hitrate"
,
websocket
.
Handler
(
hitrateHandler
))
url
:=
"http://"
+
listener
.
Addr
()
.
String
()
+
"/"
openbrowser
(
url
)
fmt
.
Println
(
"Listening at"
,
url
)
http
.
Serve
(
listener
,
nil
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment