diff --git a/System/Remote/Json.hs b/System/Remote/Json.hs
index 5f6dcd4..3269c8f 100644
--- a/System/Remote/Json.hs
+++ b/System/Remote/Json.hs
@@ -18,4 +18,4 @@ encodeAll = A.encode . Json.sampleToJson
-- | Encode metric a JSON object. See 'Json.valueToJson'
-- for a description of the encoding.
encodeOne :: Value -> L.ByteString
-encodeOne = A.encode . Json.valueToJson
+encodeOne = A.encode . Json.valueToJson []
diff --git a/System/Remote/Monitoring.hs b/System/Remote/Monitoring.hs
index 5d31658..8e67c01 100644
--- a/System/Remote/Monitoring.hs
+++ b/System/Remote/Monitoring.hs
@@ -250,35 +250,46 @@ forkServerWith store host port = do
-- | Return a new, zero-initialized counter associated with the given
-- name and server. Multiple calls to 'getCounter' with the same
-- arguments will result in an 'error'.
-getCounter :: T.Text -- ^ Counter name
- -> Server -- ^ Server that will serve the counter
+getCounter :: T.Text -- ^ Counter name
+ -> [T.Text] -- ^ Dimension names
+ -> Server -- ^ Server that will serve the counter
-> IO Counter.Counter
-getCounter name server = Metrics.createCounter name (serverMetricStore server)
+getCounter name dims server =
+ Metrics.createCounter (Metrics.dimensional name dims)
+ (serverMetricStore server)
-- | Return a new, zero-initialized gauge associated with the given
-- name and server. Multiple calls to 'getGauge' with the same
-- arguments will result in an 'error'.
-getGauge :: T.Text -- ^ Gauge name
- -> Server -- ^ Server that will serve the gauge
+getGauge :: T.Text -- ^ Gauge name
+ -> [T.Text] -- ^ Dimension names
+ -> Server -- ^ Server that will serve the gauge
-> IO Gauge.Gauge
-getGauge name server = Metrics.createGauge name (serverMetricStore server)
+getGauge name dims server =
+ Metrics.createGauge (Metrics.dimensional name dims)
+ (serverMetricStore server)
-- | Return a new, empty label associated with the given name and
-- server. Multiple calls to 'getLabel' with the same arguments will
-- result in an 'error'.
getLabel :: T.Text -- ^ Label name
+ -> [T.Text] -- ^ Dimension names
-> Server -- ^ Server that will serve the label
-> IO Label.Label
-getLabel name server = Metrics.createLabel name (serverMetricStore server)
+getLabel name dims server =
+ Metrics.createLabel (Metrics.dimensional name dims)
+ (serverMetricStore server)
-- | Return a new distribution associated with the given name and
-- server. Multiple calls to 'getDistribution' with the same arguments
-- will result in an 'error'.
-getDistribution :: T.Text -- ^ Distribution name
- -> Server -- ^ Server that will serve the distribution
+getDistribution :: T.Text -- ^ Distribution name
+ -> [T.Text] -- ^ Dimension names
+ -> Server -- ^ Server that will serve the distribution
-> IO Distribution.Distribution
-getDistribution name server =
- Metrics.createDistribution name (serverMetricStore server)
+getDistribution name dims server =
+ Metrics.createDistribution (Metrics.dimensional name dims)
+ (serverMetricStore server)
------------------------------------------------------------------------
-- Backwards compatibility shims
diff --git a/System/Remote/Snap.hs b/System/Remote/Snap.hs
index 6ac25d6..b02e1bb 100644
--- a/System/Remote/Snap.hs
+++ b/System/Remote/Snap.hs
@@ -12,16 +12,16 @@ import qualified Data.ByteString.Char8 as S8
import Data.Function (on)
import qualified Data.HashMap.Strict as M
import qualified Data.List as List
+import qualified Data.Map.Lazy as LMap
import qualified Data.Text.Encoding as T
import Data.Word (Word8)
import Network.Socket (NameInfoFlag(NI_NUMERICHOST), addrAddress, getAddrInfo,
getNameInfo)
import Paths_ekg (getDataDir)
import Prelude hiding (read)
-import Snap.Core (MonadSnap, Request, Snap, finishWith, getHeader, getRequest,
- getResponse, method, Method(GET), modifyResponse, pass,
- rqPathInfo, setContentType, setResponseStatus,
- writeLBS)
+import Snap.Core (MonadSnap, Request, Snap, finishWith, getHeader, getQueryParams,
+ getRequest, getResponse, method, Method(GET), modifyResponse,
+ pass, rqPathInfo, setContentType, setResponseStatus, writeLBS)
import Snap.Http.Server (httpServe)
import qualified Snap.Http.Server.Config as Config
import Snap.Util.FileServe (serveDirectory)
@@ -103,17 +103,22 @@ serve store = do
serveAll = do
metrics <- liftIO $ sampleAll store
writeLBS $ encodeAll metrics
+ -- consider deprecating this feature?
serveOne pathInfo = do
+ params <- getQueryParams
let segments = S8.split '/' pathInfo
nameBytes = S8.intercalate "." segments
- case T.decodeUtf8' nameBytes of
+ dims = maybe [] id (LMap.lookup "d" params)
+ decoded = (,) <$> (T.decodeUtf8' nameBytes)
+ <*> (traverse T.decodeUtf8' dims)
+ case decoded of
Left _ -> do
modifyResponse $ setResponseStatus 400 "Bad Request"
r <- getResponse
finishWith r
- Right name -> do
+ Right (name, dimensions) -> do
metrics <- liftIO $ sampleAll store
- case M.lookup name metrics of
+ case M.lookup (dimensional name dimensions) metrics of
Nothing -> pass
Just metric -> writeLBS $ encodeOne metric
diff --git a/assets/monitor.js b/assets/monitor.js
index 558334f..1d88fd8 100644
--- a/assets/monitor.js
+++ b/assets/monitor.js
@@ -115,7 +115,7 @@ $(document).ready(function () {
}
alertVisible = false;
for (var i = 0; i < listeners.length; i++) {
- listeners[i](stats, stats.ekg.server_timestamp_ms.val);
+ listeners[i](stats, stats.ekg.server_timestamp_ms[0].val);
}
}
@@ -195,11 +195,13 @@ $(document).ready(function () {
function addDynamicPlot(key, button, graph_fn, label_fn) {
function getStats(stats, time, prev_stats, prev_time) {
- return graph_fn(key, stats, time, prev_stats, prev_time);
+ return graph_fn(stats, time, prev_stats, prev_time);
}
// jQuery has problem with IDs containing dots.
- var plotId = key.replace(/\./g, "-") + "-plot";
+ var plotId = key.replace(/\./g, "-")
+ .replace(/ /g,"__")
+ .replace(/:/g,"_") + "-plot";
$("#plots:last").append(
'
' +
'

' + key +
@@ -234,20 +236,50 @@ $(document).ready(function () {
var DISTRIBUTION = "d";
var metrics = {};
- function makeDataGetter(key) {
- var pieces = key.split(".");
- function get(key, stats, time, prev_stats, prev_time) {
- var value = stats;
- $.each(pieces, function(unused_index, piece) {
- value = value[piece];
- });
+ // Utility function to test for arrays of strings equality.
+ var sameDimensions = function(xs, ys) {
+ if (xs.length != ys.length) {
+ return false;
+ }
+ var ret = true;
+ $.each(xs, function(xy_index, x) {
+ var y = ys[xy_index];
+ if (x != y) {
+ ret = false;
+ return;
+ }
+ })
+ return ret;
+ }
+
+ var lookupStat = function(name, dims, stats) {
+ var pieces = name.split(".");
+ // find the nested object
+ var arrayValues = stats;
+ $.each(pieces, function(unused_index, piece) {
+ arrayValues = arrayValues[piece];
+ });
+ // find the correct dimensional break-down
+ var value = undefined;
+ $.each(arrayValues, function(unused_index, obj) {
+ if (sameDimensions(obj.dims, dims)) {
+ value = obj;
+ }
+ })
+ return value;
+ }
+
+ function makeDataGetter(name, dims) {
+ function get(stats, time, prev_stats, prev_time) {
+ // find the nested object
+ var value = lookupStat(name, dims, stats);
+
+ // do something here
if (value.type === COUNTER) {
- if (prev_stats == undefined)
+ if (prev_stats == undefined) {
return null;
- var prev_value = prev_stats;
- $.each(pieces, function(unused_index, piece) {
- prev_value = prev_value[piece];
- });
+ }
+ var prev_value = lookupStat(name, dims, prev_stats);
return 1000 * (value.val - prev_value.val) /
(time - prev_time);
} else if (value.type === DISTRIBUTION) {
@@ -268,8 +300,9 @@ $(document).ready(function () {
}
/** Adds the table row. */
- function addElem(key, value) {
+ function addElem(name, value) {
var elem;
+ var key = name + " " + value.dims.join(" ");
if (key in metrics) {
elem = metrics[key];
} else {
@@ -284,7 +317,7 @@ $(document).ready(function () {
metrics[key] = elem;
var button = table.find("tbody > tr:last > td:first > img");
- var graph_fn = makeDataGetter(key);
+ var graph_fn = makeDataGetter(name, value.dims);
var label_fn = gaugeLabel;
if (value.type === COUNTER) {
label_fn = counterLabel;
@@ -316,12 +349,15 @@ $(document).ready(function () {
/** Updates UI for all metrics. */
function onDataReceived(stats, time) {
function build(prefix, obj) {
- $.each(obj, function (suffix, value) {
- if (value.hasOwnProperty("type")) {
- var key = prefix + suffix;
- addElem(key, value);
+ $.each(obj, function (suffix, values) {
+ var name = prefix + suffix;
+ // leaves are arrays of dimensional break-down
+ if (Array.isArray(values)) {
+ $.each(values, function (index, value) {
+ addElem(name, value);
+ });
} else {
- build(prefix + suffix + '.', value);
+ build(name + '.', values);
}
});
}
@@ -334,40 +370,40 @@ $(document).ready(function () {
function initAll() {
// Metrics
var current_bytes_used = function (stats) {
- return stats.rts.gc.current_bytes_used.val;
+ return stats.rts.gc.current_bytes_used[0].val;
};
var max_bytes_used = function (stats) {
- return stats.rts.gc.max_bytes_used.val;
+ return stats.rts.gc.max_bytes_used[0].val;
};
var max_bytes_slop = function (stats) {
- return stats.rts.gc.max_bytes_slop.val;
+ return stats.rts.gc.max_bytes_slop[0].val;
};
var current_bytes_slop = function (stats) {
- return stats.rts.gc.current_bytes_slop.val;
+ return stats.rts.gc.current_bytes_slop[0].val;
};
var productivity_wall_percent = function (stats, time, prev_stats, prev_time) {
if (prev_stats == undefined)
return null;
- var mutator_ms = stats.rts.gc.mutator_wall_ms.val -
- prev_stats.rts.gc.mutator_wall_ms.val;
- var gc_ms = stats.rts.gc.gc_wall_ms.val -
- prev_stats.rts.gc.gc_wall_ms.val;
+ var mutator_ms = stats.rts.gc.mutator_wall_ms[0].val -
+ prev_stats.rts.gc.mutator_wall_ms[0].val;
+ var gc_ms = stats.rts.gc.gc_wall_ms[0].val -
+ prev_stats.rts.gc.gc_wall_ms[0].val;
return 100 * mutator_ms / (mutator_ms + gc_ms);
};
var productivity_cpu_percent = function (stats, time, prev_stats, prev_time) {
if (prev_stats == undefined)
return null;
- var mutator_ms = stats.rts.gc.mutator_cpu_ms.val -
- prev_stats.rts.gc.mutator_cpu_ms.val;
- var gc_ms = stats.rts.gc.gc_cpu_ms.val -
- prev_stats.rts.gc.gc_cpu_ms.val;
+ var mutator_ms = stats.rts.gc.mutator_cpu_ms[0].val -
+ prev_stats.rts.gc.mutator_cpu_ms[0].val;
+ var gc_ms = stats.rts.gc.gc_cpu_ms[0].val -
+ prev_stats.rts.gc.gc_cpu_ms[0].val;
return 100 * mutator_ms / (mutator_ms + gc_ms);
};
var allocation_rate = function (stats, time, prev_stats, prev_time) {
if (prev_stats == undefined)
return null;
- return 1000 * (stats.rts.gc.bytes_allocated.val -
- prev_stats.rts.gc.bytes_allocated.val) /
+ return 1000 * (stats.rts.gc.bytes_allocated[0].val -
+ prev_stats.rts.gc.bytes_allocated[0].val) /
(time - prev_time);
};
diff --git a/ekg.cabal b/ekg.cabal
index ab59df8..e069d79 100644
--- a/ekg.cabal
+++ b/ekg.cabal
@@ -1,5 +1,5 @@
name: ekg
-version: 0.4.0.13
+version: 0.5.0.0
synopsis: Remote monitoring of processes
description:
This library lets you remotely monitor a running process over HTTP.
@@ -41,8 +41,9 @@ library
aeson < 1.3,
base >= 4.5 && < 4.10,
bytestring < 1.0,
- ekg-core >= 0.1 && < 0.2,
- ekg-json >= 0.1 && < 0.2,
+ containers >= 0.5,
+ ekg-core >= 0.2 && < 0.3,
+ ekg-json >= 0.2 && < 0.3,
filepath < 1.5,
network < 2.7,
snap-core < 1.1,
diff --git a/examples/Basic.hs b/examples/Basic.hs
index 17aee19..b8729d2 100644
--- a/examples/Basic.hs
+++ b/examples/Basic.hs
@@ -22,18 +22,26 @@ mean xs = sum' xs / fromIntegral (length xs)
main :: IO ()
main = do
- handle <- forkServer "localhost" 8000
- counter <- getCounter "iterations" handle
- label <- getLabel "args" handle
- event <- getDistribution "runtime" handle
+ handle <- forkServer "0.0.0.0" 8000
+ baseCounter <- getCounter "iterations" [] handle
+ fizzCounter <- getCounter "iterations" ["dim0:fizz"] handle
+ buzzCounter <- getCounter "iterations" ["dim0:buzz"] handle
+ fizzbuzzCounter <- getCounter "iterations" ["dim0:fizzbuzz"] handle
+ label <- getLabel "args" [] handle
+ event <- getDistribution "runtime" ["123", "456"] handle
Label.set label "some text string"
- let loop n = do
- t <- timed $ evaluate $ mean [1..n]
+ let counter n
+ | n `mod` 15 == 0 = fizzbuzzCounter
+ | n `mod` 5 == 0 = fizzCounter
+ | n `mod` 3 == 0 = buzzCounter
+ | otherwise = baseCounter
+ let loop n m = do
+ t <- timed $ evaluate $ mean $ fmap fromInteger [1..n]
Distribution.add event t
- threadDelay 2000
- Counter.inc counter
- loop n
- loop 1000000
+ threadDelay 200
+ Counter.inc $ counter m
+ loop n (m + 1)
+ loop (1000000 :: Integer) 0
timed :: IO a -> IO Double
timed m = do