1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

major caching improvements

This commit is contained in:
checktheroads
2020-04-18 07:58:46 -07:00
parent 2895d3ae46
commit caf294a36c
7 changed files with 80 additions and 10 deletions

View File

@@ -3,6 +3,8 @@
# Standard Library # Standard Library
import json import json
import hashlib import hashlib
import secrets
from datetime import datetime
# Third Party # Third Party
from pydantic import BaseModel, StrictStr, validator from pydantic import BaseModel, StrictStr, validator
@@ -57,6 +59,7 @@ class Query(BaseModel):
class Config: class Config:
"""Pydantic model configuration.""" """Pydantic model configuration."""
extra = "allow"
fields = { fields = {
"query_location": { "query_location": {
"title": params.web.text.query_location, "title": params.web.text.query_location,
@@ -83,10 +86,29 @@ class Query(BaseModel):
"x-code-samples": [{"lang": "Python", "source": "print('stuff')"}] "x-code-samples": [{"lang": "Python", "source": "print('stuff')"}]
} }
def __init__(self, **kwargs):
"""Initialize the query with a UTC timestamp at initialization time."""
super().__init__(**kwargs)
self.timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
def __repr__(self):
"""Represent only the query fields."""
return (
f"Query(query_location={str(self.query_location)}, "
f"query_type={str(self.query_type)}, query_vrf={str(self.query_vrf)}, "
f"query_target={str(self.query_target)})"
)
def digest(self): def digest(self):
"""Create SHA256 hash digest of model representation.""" """Create SHA256 hash digest of model representation."""
return hashlib.sha256(repr(self).encode()).hexdigest() return hashlib.sha256(repr(self).encode()).hexdigest()
def random(self):
"""Create a random string to prevent client or proxy caching."""
return hashlib.sha256(
secrets.token_bytes(8) + repr(self).encode() + secrets.token_bytes(8)
).hexdigest()
@property @property
def summary(self): def summary(self):
"""Create abbreviated representation of instance.""" """Create abbreviated representation of instance."""

View File

@@ -57,10 +57,11 @@ class QueryResponse(BaseModel):
output: StrictStr output: StrictStr
level: constr(regex=r"success") = "success" level: constr(regex=r"success") = "success"
id: StrictStr random: StrictStr
cached: StrictBool cached: StrictBool
runtime: StrictInt runtime: StrictInt
keywords: List[StrictStr] = [] keywords: List[StrictStr] = []
timestamp: StrictStr
class Config: class Config:
"""Pydantic model configuration.""" """Pydantic model configuration."""
@@ -69,6 +70,25 @@ class QueryResponse(BaseModel):
description = "Looking glass response" description = "Looking glass response"
fields = { fields = {
"level": {"title": "Level", "description": "Severity"}, "level": {"title": "Level", "description": "Severity"},
"cached": {
"title": "Cached",
"description": "`true` if the response is from a previously cached query.",
},
"random": {
"title": "Random",
"description": "Random string to prevent client or intermediate caching.",
"example": "504cbdb47eb8310ca237bf512c3e10b44b0a3d85868c4b64a20037dc1c3ef857",
},
"runtime": {
"title": "Runtime",
"description": "Time it took to run the query in seconds.",
"example": 6,
},
"timestamp": {
"title": "Timestamp",
"description": "UTC Time at which the backend application received the query.",
"example": "2020-04-18 14:45:37",
},
"keywords": { "keywords": {
"title": "Keywords", "title": "Keywords",
"description": "Relevant keyword values contained in the `output` field, which can be used for formatting.", "description": "Relevant keyword values contained in the `output` field, which can be used for formatting.",

View File

@@ -71,7 +71,7 @@ async def query(query_data: Query, request: Request):
log.info(f"Starting query execution for query {query_data.summary}") log.info(f"Starting query execution for query {query_data.summary}")
cache_response = await cache.get(cache_key) cache_response = await cache.get_dict(cache_key, "output")
cached = False cached = False
if cache_response: if cache_response:
@@ -80,6 +80,7 @@ async def query(query_data: Query, request: Request):
cached = True cached = True
runtime = 0 runtime = 0
timestamp = await cache.get_dict(cache_key, "timestamp")
elif not cache_response: elif not cache_response:
log.debug(f"No existing cache entry for query {cache_key}") log.debug(f"No existing cache entry for query {cache_key}")
@@ -87,18 +88,20 @@ async def query(query_data: Query, request: Request):
f"Created new cache key {cache_key} entry for query {query_data.summary}" f"Created new cache key {cache_key} entry for query {query_data.summary}"
) )
timestamp = query_data.timestamp
# Pass request to execution module # Pass request to execution module
starttime = time.time() starttime = time.time()
cache_value = await Execute(query_data).response() cache_output = await Execute(query_data).response()
endtime = time.time() endtime = time.time()
elapsedtime = round(endtime - starttime, 4) elapsedtime = round(endtime - starttime, 4)
log.debug(f"Query {cache_key} took {elapsedtime} seconds to run.") log.debug(f"Query {cache_key} took {elapsedtime} seconds to run.")
if cache_value is None: if cache_output is None:
raise HyperglassError(message=params.messages.general, alert="danger") raise HyperglassError(message=params.messages.general, alert="danger")
# Create a cache entry # Create a cache entry
await cache.set(cache_key, str(cache_value)) await cache.set_dict(cache_key, "output", str(cache_output))
await cache.set_dict(cache_key, "timestamp", timestamp)
await cache.expire(cache_key, seconds=cache_timeout) await cache.expire(cache_key, seconds=cache_timeout)
log.debug(f"Added cache entry for query: {cache_key}") log.debug(f"Added cache entry for query: {cache_key}")
@@ -106,7 +109,7 @@ async def query(query_data: Query, request: Request):
runtime = int(round(elapsedtime, 0)) runtime = int(round(elapsedtime, 0))
# If it does, return the cached entry # If it does, return the cached entry
cache_response = await cache.get(cache_key) cache_response = await cache.get_dict(cache_key, "output")
log.debug(f"Cache match for {cache_key}:\n {cache_response}") log.debug(f"Cache match for {cache_key}:\n {cache_response}")
log.success(f"Completed query execution for {query_data.summary}") log.success(f"Completed query execution for {query_data.summary}")
@@ -116,6 +119,8 @@ async def query(query_data: Query, request: Request):
"id": cache_key, "id": cache_key,
"cached": cached, "cached": cached,
"runtime": runtime, "runtime": runtime,
"timestamp": timestamp,
"random": query_data.random(),
"level": "success", "level": "success",
"keywords": [], "keywords": [],
} }

View File

@@ -71,10 +71,22 @@ class Cache:
raw = await self.instance.mget(args) raw = await self.instance.mget(args)
return await self._parse_types(raw) return await self._parse_types(raw)
async def get_dict(self, key, field=None):
"""Get hash map (dict) item(s)."""
if field is None:
raw = await self.instance.hgetall(key)
else:
raw = await self.instance.hget(key, field)
return await self._parse_types(raw)
async def set(self, key, value): async def set(self, key, value):
"""Set cache values.""" """Set cache values."""
return await self.instance.set(key, value) return await self.instance.set(key, value)
async def set_dict(self, key, field, value):
"""Set hash map (dict) values."""
return await self.instance.hset(key, field, value)
async def wait(self, pubsub, timeout=30, **kwargs): async def wait(self, pubsub, timeout=30, **kwargs):
"""Wait for pub/sub messages & return posted message.""" """Wait for pub/sub messages & return posted message."""
now = time.time() now = time.time()

View File

@@ -139,7 +139,7 @@ class Text(HyperglassModel):
query_vrf: StrictStr = "Routing Table" query_vrf: StrictStr = "Routing Table"
fqdn_tooltip: StrictStr = "Use {protocol}" # Formatted by Javascript fqdn_tooltip: StrictStr = "Use {protocol}" # Formatted by Javascript
cache_prefix: StrictStr = "Results cached for " cache_prefix: StrictStr = "Results cached for "
cache_icon: StrictStr = "Cached Response" cache_icon: StrictStr = "Cached Response from {time}" # Formatted by Javascript
complete_time: StrictStr = "Completed in {seconds}" # Formatted by Javascript complete_time: StrictStr = "Completed in {seconds}" # Formatted by Javascript
@validator("title_mode") @validator("title_mode")

View File

@@ -16,6 +16,7 @@ import styled from "@emotion/styled";
import LightningBolt from "~/components/icons/LightningBolt"; import LightningBolt from "~/components/icons/LightningBolt";
import useAxios from "axios-hooks"; import useAxios from "axios-hooks";
import strReplace from "react-string-replace"; import strReplace from "react-string-replace";
import format from "string-format";
import { startCase } from "lodash"; import { startCase } from "lodash";
import useConfig from "~/components/HyperglassProvider"; import useConfig from "~/components/HyperglassProvider";
import useMedia from "~/components/MediaProvider"; import useMedia from "~/components/MediaProvider";
@@ -24,6 +25,8 @@ import RequeryButton from "~/components/RequeryButton";
import ResultHeader from "~/components/ResultHeader"; import ResultHeader from "~/components/ResultHeader";
import CacheTimeout from "~/components/CacheTimeout"; import CacheTimeout from "~/components/CacheTimeout";
format.extend(String.prototype, {});
const FormattedError = ({ keywords, message }) => { const FormattedError = ({ keywords, message }) => {
const patternStr = keywords.map((kw) => `(${kw})`).join("|"); const patternStr = keywords.map((kw) => `(${kw})`).join("|");
const pattern = new RegExp(patternStr, "gi"); const pattern = new RegExp(patternStr, "gi");
@@ -125,7 +128,11 @@ const Result = React.forwardRef(
<> <>
<CacheTimeout timeout={config.cache.timeout} text={config.web.text.cache_prefix} /> <CacheTimeout timeout={config.cache.timeout} text={config.web.text.cache_prefix} />
{data?.cached && ( {data?.cached && (
<Tooltip hasArrow label={config.web.text.cache_icon} placement="top"> <Tooltip
hasArrow
label={config.web.text.cache_icon.format({ time: data?.timestamp })}
placement="top"
>
<Box ml={1}> <Box ml={1}>
<LightningBolt color={color[colorMode]} /> <LightningBolt color={color[colorMode]} />
</Box> </Box>
@@ -136,7 +143,11 @@ const Result = React.forwardRef(
const cacheSm = ( const cacheSm = (
<> <>
{data?.cached && ( {data?.cached && (
<Tooltip hasArrow label={config.web.text.cache_icon} placement="top"> <Tooltip
hasArrow
label={config.web.text.cache_icon.format({ time: data?.timestamp })}
placement="top"
>
<Box mr={1}> <Box mr={1}>
<LightningBolt color={color[colorMode]} /> <LightningBolt color={color[colorMode]} />
</Box> </Box>

View File

@@ -7,7 +7,7 @@ format.extend(String.prototype, {});
const runtimeText = (runtime, text) => { const runtimeText = (runtime, text) => {
let unit; let unit;
if (runtime > 1) { if (runtime === 1) {
unit = "seconds"; unit = "seconds";
} else { } else {
unit = "second"; unit = "second";