Skip to content

Commit

Permalink
fix: locust files
Browse files Browse the repository at this point in the history
  • Loading branch information
Datron committed Oct 17, 2024
1 parent ad33c31 commit 6709963
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ dist/

# Log files
*.log

locust/venv
locust/__pycache__
# cargo
target/

Expand Down
2 changes: 1 addition & 1 deletion crates/context_aware_config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ strum_macros = { workspace = true }
superposition_macros = { path = "../superposition_macros" }
superposition_types = { path = "../superposition_types", features = ["result"] }
uuid = { workspace = true }
fred = { workspace = true, optional = true }
fred = { workspace = true, optional = true, features = ["metrics"]}
cfg-if = { workspace = true }

[features]
Expand Down
62 changes: 54 additions & 8 deletions crates/context_aware_config/src/api/config/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,35 +558,81 @@ async fn get_config_fast(
tenant: Tenant,
state: Data<AppState>,
) -> superposition::Result<HttpResponse> {
use fred::interfaces::MetricsInterface;

log::debug!("Started redis fetch");
let config_key = format!("{}::cac_config", *tenant);
let max_created_key = format!("{}::cac_config::max_created_at", *tenant);
let audit_id_key = format!("{}::cac_config::audit_id", *tenant);
let config_version_key = format!("{}::cac_config::config_version", *tenant);
match state.redis.get::<String, String>(config_key.clone()).await {
let client = state.redis.next_connected();
let config = client.get::<String, String>(config_key).await;
let metrics = client.take_latency_metrics();
let network_metrics = client.take_network_latency_metrics();
log::trace!(
"Network metrics for config fetch in milliseconds :: max: {}, min: {}, avg: {}; Latency metrics :: max: {}, min: {}, avg: {}",
network_metrics.max,
network_metrics.min,
network_metrics.avg,
metrics.max,
metrics.min,
metrics.avg
);
match config {
Ok(config) => {
let mut response = HttpResponse::Ok();
if let Ok(max_created_at) =
state.redis.get::<String, String>(max_created_key).await
client.get::<String, String>(max_created_key).await
{
let metrics = client.take_latency_metrics();
let network_metrics = client.take_network_latency_metrics();
log::trace!(
"Network metrics max-created-by fetch in milliseconds :: max: {}, min: {}, avg: {}; Latency metrics :: max: {}, min: {}, avg: {}",
network_metrics.max,
network_metrics.min,
network_metrics.avg,
metrics.max,
metrics.min,
metrics.avg
);
response
.insert_header((AppHeader::LastModified.to_string(), max_created_at));
}
if let Ok(audit_id) = state.redis.get::<String, String>(audit_id_key).await {
if let Ok(audit_id) = client.get::<String, String>(audit_id_key).await {
let metrics = client.take_latency_metrics();
let network_metrics = client.take_network_latency_metrics();
log::trace!(
"Network metrics for audit ID in milliseconds :: max: {}, min: {}, avg: {}; Latency metrics :: max: {}, min: {}, avg: {}",
network_metrics.max,
network_metrics.min,
network_metrics.avg,
metrics.max,
metrics.min,
metrics.avg
);
response.insert_header((AppHeader::XAuditId.to_string(), audit_id));
}
if let Ok(config_version) = state
.redis
.get::<Option<i64>, String>(config_version_key)
.await
if let Ok(config_version) =
client.get::<Option<i64>, String>(config_version_key).await
{
let metrics = client.take_latency_metrics();
let network_metrics = client.take_network_latency_metrics();
log::trace!(
"Network metrics for version ID in milliseconds :: max: {}, min: {}, avg: {}; Latency metrics :: max: {}, min: {}, avg: {}",
network_metrics.max,
network_metrics.min,
network_metrics.avg,
metrics.max,
metrics.min,
metrics.avg
);
add_config_version_to_header(&config_version, &mut response);
}
response.insert_header(ContentType::json());
Ok(response.body(config))
}
Err(err) => {
log::info!("Could not get config in redis due to {}", err);
log::error!("Could not get config in redis due to {}", err);
Err(response_error!(
StatusCode::INTERNAL_SERVER_ERROR,
"could not fetch config, please try /config API"
Expand Down
1 change: 0 additions & 1 deletion crates/superposition/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use actix_web::{
web::{self, get, scope, Data, PathConfig},
App, HttpMessage, HttpResponse, HttpServer,
};
// #[cfg(feature = "high-performance-mode")]
use context_aware_config::api::*;
use experimentation_platform::api::*;
use frontend::app::*;
Expand Down
26 changes: 26 additions & 0 deletions locust/locustfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from locust import HttpUser, task, constant_pacing
import random

tenants = ["mjos", "sdkbuildconfig", "sdkunified", "dashboard"]

class SuperpositionLoad(HttpUser):
# wait_time = constant_pacing(1)

def call_endpoint(self, tenant):
config = self.client.get("/config", name="get config for {}".format(tenant), headers={'x-tenant': tenant, 'x-feature': ''}).json
# print("config for {}\n {}".format(tenant, config))

def call_endpoint_fast(self, tenant):
config = self.client.get("/config/fast", name="get cached config for {}".format(tenant), headers={'x-tenant': tenant, 'x-feature': ''}).json
# print("config for {}\n {}".format(tenant, config))

# @task
# def load_config(self):
# tenant = random.choice(tenants)
# self.call_endpoint(tenant)

@task
def load_config_fast(self):
tenant = random.choice(tenants)
self.call_endpoint_fast(tenant)

58 changes: 58 additions & 0 deletions locust/plot_graph_from_logs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import os
import json
import matplotlib.pyplot as plt
from collections import defaultdict

def read_json_objects_from_files(directory):
path_latency_map = defaultdict(list)

for filename in os.listdir(directory):
if filename.endswith(".log"):
file_path = os.path.join(directory, filename)
with open(file_path, 'r') as f:
for line in f:
try:
obj = json.loads(line.strip())

path = obj.get('span', {}).get('path')
latency = obj.get('fields', {}).get('latency')
if path and latency is not None:
latency_number = int(latency.split(" ")[0])
# print(f"obj in line {path} {latency_number}")
path_latency_map[path].append(latency_number)
# exit(1)

except json.JSONDecodeError:
print(f"Skipping invalid JSON line in {filename}")

return path_latency_map

def plot_latency(path_latency_map):
plt.figure(figsize=(10, 6))

db_latency = path_latency_map.get("/superposition/config", [])
redis_latency = path_latency_map.get("/superposition/config/fast", [])

if db_latency:
plt.plot(db_latency, label="DB", color='blue')
if redis_latency:
plt.plot(redis_latency, label="Redis", color='red')

plt.xlabel('Request Count')
plt.ylabel('Latency (ms)')
plt.title('Latency comparison between DB and Redis')
plt.legend(loc='upper right')
upper_limit = max(max(db_latency, default=0), max(redis_latency, default=0))
# print(f"upper_limit {upper_limit}")
# plt.ylim(0, upper_limit * 1.1)
# plt.xlim(0, upper_limit * 1.1)
plt.grid(False)
output_file = "server_latency.png"
plt.savefig(output_file, format='png')
print(f"Plot saved to {output_file}")
plt.close()

directory = '<dir>'
path_latency_map = read_json_objects_from_files(directory)
# print(f"path latency map {path_latency_map}")
plot_latency(path_latency_map)
180 changes: 180 additions & 0 deletions locust/redis_op_vs_network_latency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import json
import os
import re
import matplotlib.pyplot as plt
from datetime import datetime
import seaborn as sns
import matplotlib.dates as mdates

def extract_metrics(message):
match = re.search(r'(Network|Latency) metrics .* max:\s*(\d+),\s*min:\s*(\d+),\s*avg:\s*(\d+)', message)
if match:
metric_type = match.group(1)
max_val = int(match.group(2))
min_val = int(match.group(3))
avg_val = int(match.group(4))
return metric_type, max_val, min_val, avg_val
return None, None, None, None

def extract_data_from_files(directory):
timestamps = []
network_max_metrics = []
network_min_metrics = []
network_avg_metrics = []

latency_max_metrics = []
latency_min_metrics = []
latency_avg_metrics = []

for filename in os.listdir(directory):
filepath = os.path.join(directory, filename)

with open(filepath, 'r') as f:
for line in f:
try:
data = json.loads(line)
timestamp = data['timestamp']
message = data['fields']['message']

metric_type, max_val, min_val, avg_val = extract_metrics(message)

if metric_type is not None:
timestamps.append(datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ"))

if metric_type == "Network":
network_max_metrics.append(max_val)
network_min_metrics.append(min_val)
network_avg_metrics.append(avg_val)
elif metric_type == "Latency":
latency_max_metrics.append(max_val)
latency_min_metrics.append(min_val)
latency_avg_metrics.append(avg_val)
except json.JSONDecodeError:
print(f"Error decoding JSON in file {filename}")
except KeyError:
print(f"KeyError: Missing expected keys in file {filename}")

return (timestamps, network_max_metrics, network_min_metrics, network_avg_metrics,
latency_max_metrics, latency_min_metrics, latency_avg_metrics)

def plot_metrics(timestamps, network_max, network_min, network_avg, latency_max, latency_min, latency_avg):
sns.set_theme(style="whitegrid", palette="pastel") # Set seaborn style and palette

fig, ax1 = plt.subplots(figsize=(12, 8))

# Plot Network metrics on the first axis (left y-axis)
sns.lineplot(x=timestamps, y=network_max, ax=ax1, label='Network Max Latency (ms)', color='red', marker='o')
sns.lineplot(x=timestamps, y=network_min, ax=ax1, label='Network Min Latency (ms)', color='blue', marker='x', linestyle='--')
sns.lineplot(x=timestamps, y=network_avg, ax=ax1, label='Network Avg Latency (ms)', color='green', marker='s', linestyle=':')

ax1.set_xlabel('Timestamp', fontsize=12)
ax1.set_ylabel('Network Latency (ms)', fontsize=12, color='blue')
ax1.tick_params(axis='y', labelcolor='blue')
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H:%M'))
ax1.xaxis.set_major_locator(mdates.AutoDateLocator())
plt.xticks(rotation=45)

# Create a second y-axis to plot Latency metrics
ax2 = ax1.twinx()
sns.lineplot(x=timestamps, y=latency_max, ax=ax2, label='Latency Max (ms)', color='purple', marker='o', linestyle='-')
sns.lineplot(x=timestamps, y=latency_min, ax=ax2, label='Latency Min (ms)', color='cyan', marker='x', linestyle='--')
sns.lineplot(x=timestamps, y=latency_avg, ax=ax2, label='Latency Avg (ms)', color='yellow', marker='s', linestyle=':')

ax2.set_ylabel('Latency (ms)', fontsize=12, color='green')
ax2.tick_params(axis='y', labelcolor='green')

plt.title('Network and Latency Metrics Over Time', fontsize=14)
ax1.grid(True)

lines_1, labels_1 = ax1.get_legend_handles_labels()
lines_2, labels_2 = ax2.get_legend_handles_labels()
ax1.legend(lines_1 + lines_2, labels_1 + labels_2, loc='upper left')

fig.tight_layout()
output_file = "redis_network_op_latency.png"
plt.savefig(output_file, format='png')
print(f"Plot saved to {output_file}")

def plot_histograms(network_max, network_min, network_avg, latency_max, latency_min, latency_avg):
sns.set_theme(style="whitegrid", palette="pastel") # Set Seaborn style and palette

# Create a figure with multiple subplots for each metric type
fig, axs = plt.subplots(2, 3, figsize=(18, 10))

# Plot histograms for Network metrics
sns.histplot(network_max, kde=True, color="blue", ax=axs[0, 0])
axs[0, 0].set_title('Network Max Latency Distribution')

sns.histplot(network_min, kde=True, color="blue", ax=axs[0, 1])
axs[0, 1].set_title('Network Min Latency Distribution')

sns.histplot(network_avg, kde=True, color="blue", ax=axs[0, 2])
axs[0, 2].set_title('Network Avg Latency Distribution')

# Plot histograms for Latency metrics
sns.histplot(latency_max, kde=True, color="green", ax=axs[1, 0])
axs[1, 0].set_title('Latency Max Distribution')

sns.histplot(latency_min, kde=True, color="green", ax=axs[1, 1])
axs[1, 1].set_title('Latency Min Distribution')

sns.histplot(latency_avg, kde=True, color="green", ax=axs[1, 2])
axs[1, 2].set_title('Latency Avg Distribution')

# Set general plot properties
for ax in axs.flat:
ax.set_xlabel('Milliseconds')
ax.set_ylabel('Frequency')

plt.tight_layout()
output_file = "redis_network_op_latency_hist.png"
plt.savefig(output_file, format='png')
print(f"Plot saved to {output_file}")

def plot_boxplots(network_max, network_min, network_avg, latency_max, latency_min, latency_avg):
sns.set_theme(style="whitegrid", palette="pastel") # Set Seaborn style and palette

# Prepare the data for boxplots (each list of metrics will be part of a category)
metrics_data = [
network_max, network_min, network_avg,
latency_max, latency_min, latency_avg
]
metrics_labels = ['Network Max', 'Network Min', 'Network Avg', 'Latency Max', 'Latency Min', 'Latency Avg']

# Create a figure with one subplot for the boxplot
plt.figure(figsize=(12, 8))

# Plot the boxplot with spines
sns.boxplot(data=metrics_data)

# Add titles and labels
plt.title('Distribution of Network and Latency Metrics', fontsize=14)
plt.xticks(range(6), metrics_labels, fontsize=12)
plt.ylabel('Latency (ms)', fontsize=12)

# Customize spines to make them more visible
sns.despine(offset=10, trim=True)
output_file = "redis_network_op_latency_box.png"
plt.savefig(output_file, format='png')
print(f"Plot saved to {output_file}")

def main():
directory = '<dir>'
(timestamps, network_max, network_min, network_avg, latency_max, latency_min, latency_avg) = extract_data_from_files(directory)
# print(f"timestamps: {timestamps}")
# print(f"network_max: {network_max}")
# print(f"network_min: {network_min}")
# print(f"network_avg: {network_avg}")
# print(f"latency_max: {latency_max}")
# print(f"latency_min: {latency_min}")
# print(f"latency_avg: {latency_avg}")
size = min([len(timestamps), len(network_max), len(network_min), len(network_avg), len(latency_max), len(latency_min), len(latency_avg)])
if timestamps:
plot_metrics(timestamps[:size], network_max[:size], network_min[:size], network_avg[:size], latency_max[:size], latency_min[:size], latency_avg[:size])
plot_histograms(network_max[:size], network_min[:size], network_avg[:size], latency_max[:size], latency_min[:size], latency_avg[:size])
plot_boxplots(network_max[:size], network_min[:size], network_avg[:size], latency_max[:size], latency_min[:size], latency_avg[:size])
else:
print("No valid data found.")

if __name__ == '__main__':
main()
Loading

0 comments on commit 6709963

Please sign in to comment.