Skip to content

Commit e3faa21

Browse files
committed
Add blobstore benchmark job for performance testing
1 parent c856fea commit e3faa21

12 files changed

+728
-0
lines changed

jobs/blobstore_benchmark/monit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Empty because it's an errand

jobs/blobstore_benchmark/spec

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
name: blobstore_benchmark
3+
description: "Cloud Controller blobstore benchmark runner (errand)"
4+
5+
templates:
6+
bpm.yml.erb: config/bpm.yml
7+
bin/run.erb: bin/run
8+
cloud_controller_ng.yml.erb: config/cloud_controller_ng.yml
9+
ruby_version.sh.erb: bin/ruby_version.sh
10+
db_ca.crt.erb: config/certs/db_ca.crt
11+
storage_cli_config_droplets.json.erb: config/storage_cli_config_droplets.json
12+
storage_cli_config_packages.json.erb: config/storage_cli_config_packages.json
13+
storage_cli_config_buildpacks.json.erb: config/storage_cli_config_buildpacks.json
14+
storage_cli_config_resource_pool.json.erb: config/storage_cli_config_resource_pool.json
15+
16+
packages:
17+
- capi_utils
18+
- cloud_controller_ng
19+
- ruby-3.2
20+
- jemalloc
21+
- storage-cli
22+
23+
24+
consumes:
25+
- name: cloud_controller_internal
26+
type: cloud_controller_internal
27+
optional: false
28+
- name: cloud_controller_db
29+
type: cloud_controller_db
30+
- name: database
31+
type: database
32+
optional: true
33+
34+
properties:
35+
blobstore_benchmark.mode:
36+
description: "Which blobstore backend to benchmark ('storage-cli' or 'fog')."
37+
default: "storage-cli"
38+
39+
blobstore_benchmark.cc_overrides:
40+
description: "Hash merged into cc config for this errand."
41+
default: {}
42+
cc:
43+
description: "Full Cloud Controller 'cc' config subtree."
44+
default: {}
45+
cc.stdout_logging_enabled:
46+
default: false
47+
description: "Enable logging to stdout"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
4+
JOB_DIR="/var/vcap/jobs/blobstore_benchmark"
5+
PKG_DIR="/var/vcap/packages/cloud_controller_ng"
6+
7+
MODE="<%= p('blobstore_benchmark.mode') %>"
8+
echo "Performing blobstore benchmarks (mode: ${MODE})"
9+
10+
export LD_PRELOAD=/var/vcap/packages/jemalloc/lib/libjemalloc.so
11+
12+
perform_blobstore_benchmarks() {
13+
export CLOUD_CONTROLLER_NG_CONFIG=/var/vcap/jobs/blobstore_benchmark/config/cloud_controller_ng.yml
14+
source "${JOB_DIR}/bin/ruby_version.sh"
15+
cd /var/vcap/packages/cloud_controller_ng/cloud_controller_ng
16+
17+
exec bundle exec rake benchmarks:perform_blobstore_benchmark
18+
}
19+
20+
case ${1:-} in
21+
run)
22+
perform_blobstore_benchmarks
23+
;;
24+
25+
*)
26+
/var/vcap/jobs/bpm/bin/bpm run blobstore_benchmark -p blobstore_benchmark
27+
;;
28+
29+
esac
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
processes:
2+
- name: blobstore_benchmark
3+
executable: /var/vcap/jobs/blobstore_benchmark/bin/run
4+
args:
5+
- run
6+
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<%
2+
require 'cgi'
3+
4+
def yaml_escape(input_string)
5+
chars_to_escape = /[:\\"\x00-\x1f\x7f]/
6+
chars_needing_quotes = /[ !#'&%*,:>@\[\]\\`{|}]/
7+
delimiter = (chars_needing_quotes.match(input_string) ||
8+
chars_to_escape.match(input_string)) ? '"' : ''
9+
fixed_string = input_string.gsub(/(#{chars_to_escape})/) { |m| "\\x#{'%x' % m.ord}" }
10+
"#{delimiter}#{fixed_string}#{delimiter}"
11+
end
12+
13+
def deep_merge_without_overwrite(base, extra)
14+
return base unless extra.is_a?(Hash)
15+
16+
extra.each do |k, v|
17+
if !base.key?(k)
18+
base[k] = v
19+
else
20+
if base[k].is_a?(Hash) && v.is_a?(Hash)
21+
deep_merge_without_overwrite(base[k], v)
22+
end
23+
# else: keep base[k] as-is (do not overwrite)
24+
end
25+
end
26+
27+
base
28+
end
29+
30+
cc_cfg = (link("cloud_controller_internal").p("cc") rescue {})
31+
32+
unless cc_cfg.is_a?(Hash)
33+
raise "cc link did not return a Hash, got: #{cc_cfg.class}"
34+
end
35+
36+
%w[resource_pool buildpacks packages droplets].each do |k|
37+
section = cc_cfg[k]
38+
next unless section.is_a?(Hash)
39+
40+
%w[fog_connection connection_config fog_aws_storage_options fog_gcp_storage_options webdav_config].each do |hk|
41+
section[hk] = {} if section.key?(hk) && section[hk].nil?
42+
end
43+
end
44+
45+
db = link("cloud_controller_db").p("ccdb.databases").find { |d| d["tag"] == "cc" }
46+
db_role = link("cloud_controller_db").p("ccdb.roles").find { |r| r["tag"] == "admin" }
47+
48+
database_address = nil
49+
link('cloud_controller_db').if_p('ccdb.address') { |host| database_address = host }
50+
.else { database_address = link('database').instances[0].address }
51+
52+
db_hash = {
53+
'database' => {
54+
'adapter' => (link("cloud_controller_db").p("ccdb.db_scheme") == "mysql" ? "mysql2" : link("cloud_controller_db").p("ccdb.db_scheme")),
55+
'host' => database_address,
56+
'port' => link("cloud_controller_db").p("ccdb.port"),
57+
'user' => db_role["name"],
58+
# Let YAML handle quoting/escaping correctly
59+
'password' => db_role["password"].to_s,
60+
'database' => db["name"],
61+
},
62+
'max_connections' => link("cloud_controller_db").p("ccdb.max_connections"),
63+
'pool_timeout' => link("cloud_controller_db").p("ccdb.pool_timeout"),
64+
# keep DB noise down
65+
'log_level' => (cc_cfg['db_logging_level'] || 'error'),
66+
'log_db_queries' => (cc_cfg.key?('log_db_queries') ? cc_cfg['log_db_queries'] : false),
67+
'ssl_verify_hostname' => link("cloud_controller_db").p("ccdb.ssl_verify_hostname"),
68+
'connection_validation_timeout' => link("cloud_controller_db").p("ccdb.connection_validation_timeout"),
69+
}
70+
71+
if link("cloud_controller_db").p("ccdb.ca_cert", nil)
72+
db_hash['ca_cert_path'] = '/var/vcap/jobs/blobstore_benchmark/config/certs/db_ca.crt'
73+
end
74+
75+
logging_level = p("cc.logging_level", cc_cfg["logging_level"] || cc_cfg.dig("logging", "level") || "error")
76+
77+
final = {
78+
'pid_filename' => '/var/vcap/sys/run/blobstore_benchmark/blobstore_benchmark.pid',
79+
'index' => spec.index,
80+
'name' => name,
81+
82+
'logging' => {
83+
'file' => '/var/vcap/sys/log/blobstore_benchmark/blobstore_benchmark.log',
84+
'syslog' => 'vcap.cloud_controller_ng',
85+
'level' => logging_level.to_s,
86+
'max_retries' => p("cc.logging_max_retries", cc_cfg["logging_max_retries"] || 0),
87+
'format' => {
88+
'timestamp' => (cc_cfg.dig("logging", "format", "timestamp") ||
89+
link("cloud_controller_internal").p("cc.logging.format.timestamp", "rfc3339"))
90+
},
91+
'stdout_sink_enabled' => p('cc.stdout_logging_enabled', false)
92+
},
93+
94+
'db' => db_hash,
95+
96+
'storage_cli_config_file_resource_pool' => '/var/vcap/jobs/blobstore_benchmark/config/storage_cli_config_resource_pool.json',
97+
'storage_cli_config_file_buildpacks' => '/var/vcap/jobs/blobstore_benchmark/config/storage_cli_config_buildpacks.json',
98+
'storage_cli_config_file_packages' => '/var/vcap/jobs/blobstore_benchmark/config/storage_cli_config_packages.json',
99+
'storage_cli_config_file_droplets' => '/var/vcap/jobs/blobstore_benchmark/config/storage_cli_config_droplets.json',
100+
}
101+
102+
deep_merge_without_overwrite(final, cc_cfg)
103+
%>
104+
---
105+
<%= final.to_yaml.sub(/\A---\s*\n/, '') %>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= link('cloud_controller_db').p('ccdb.ca_cert', '') %>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../shared_job_templates/ruby_version.sh.erb
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<%
2+
require "json"
3+
4+
# Ensure Azure CLI connection_config has a default timeout if none is set
5+
def cli_cfg_with_default_timeout(connection_cfg, blobstore_type, default_seconds: 41)
6+
cfg = (connection_cfg || {}).dup
7+
if blobstore_type == 'storage_cli'
8+
if !cfg.key?('put_timeout_in_seconds') || cfg['put_timeout_in_seconds'].to_s.empty?
9+
cfg['put_timeout_in_seconds'] = default_seconds.to_s
10+
end
11+
end
12+
cfg
13+
end
14+
15+
# helper: add key only when value is present
16+
def add_optional(h, key, val)
17+
return if val.nil?
18+
return if val.respond_to?(:empty?) && val.empty?
19+
h[key] = val
20+
end
21+
22+
l = link("cloud_controller_internal")
23+
24+
scope = "cc.buildpacks.connection_config"
25+
provider = l.p("cc.buildpacks.blobstore_provider", nil)
26+
options = {}
27+
28+
if provider == "AzureRM"
29+
options["provider"] = provider
30+
options["account_name"] = l.p("#{scope}.azure_storage_account_name")
31+
options["container_name"] = l.p("#{scope}.container_name")
32+
options["account_key"] = l.p("#{scope}.azure_storage_access_key")
33+
add_optional(options, "environment", l.p("#{scope}.environment", "AzureCloud"))
34+
add_optional(options, "put_timeout_in_seconds", l.p("#{scope}.put_timeout_in_seconds", nil))
35+
options = cli_cfg_with_default_timeout(options, 'storage_cli')
36+
end
37+
38+
if provider == "Google"
39+
options["provider"] = provider
40+
options["credentials_source"] = "static"
41+
options["json_key"] = l.p("#{scope}.google_json_key_string")
42+
options["bucket_name"] = l.p("#{scope}.bucket_name")
43+
add_optional(options, "storage_class", l.p("#{scope}.storage_class", nil))
44+
add_optional(options, "encryption_key", l.p("#{scope}.encryption_key", nil))
45+
end
46+
47+
if provider == "AWS"
48+
options["provider"] = provider
49+
options["bucket_name"] = l.p("#{scope}.bucket_name")
50+
options["credentials_source"] = "static"
51+
options["access_key_id"] = l.p("#{scope}.aws_access_key_id")
52+
options["secret_access_key"] = l.p("#{scope}.aws_secret_access_key")
53+
add_optional(options, "region", l.p("#{scope}.region", nil))
54+
add_optional(options, "host", l.p("#{scope}.host", nil))
55+
add_optional(options, "port", l.p("#{scope}.port", nil))
56+
add_optional(options, "ssl_verify_peer", l.p("#{scope}.ssl_verify_peer", nil))
57+
add_optional(options, "use_ssl", l.p("#{scope}.use_ssl", nil))
58+
add_optional(options, "signature_version", l.p("#{scope}.signature_version", nil))
59+
add_optional(options, "server_side_encryption", l.p("#{scope}.encryption", nil))
60+
add_optional(options, "sse_kms_key_id", l.p("#{scope}.x-amz-server-side-encryption-aws-kms-key-id", nil))
61+
add_optional(options, "multipart_upload", l.p("#{scope}.multipart_upload", nil))
62+
end
63+
64+
if provider == "aliyun"
65+
options["provider"] = provider
66+
options["access_key_id"] = l.p("#{scope}.aliyun_accesskey_id")
67+
options["access_key_secret"] = l.p("#{scope}.aliyun_accesskey_secret")
68+
options["endpoint"] = l.p("#{scope}.aliyun_oss_endpoint")
69+
options["bucket_name"] = l.p("#{scope}.aliyun_oss_bucket")
70+
end
71+
72+
if provider == "webdav"
73+
options["provider"] = provider
74+
options["user"] = l.p("#{scope}.username")
75+
options["password"] = l.p("#{scope}.password")
76+
options["endpoint"] = l.p("#{scope}.public_endpoint")
77+
add_optional(options, "secret", l.p("#{scope}.secret", nil))
78+
add_optional(options, "retry_attempts", l.p("#{scope}.retry_attempts", nil))
79+
80+
# TLS nested object with a Cert inside
81+
ca_cert=l.p("#{scope}.ca_cert",nil)
82+
unless ca_cert.empty?
83+
options["tls"]={"cert"=>ca_cert}
84+
end
85+
end
86+
87+
-%>
88+
<%= JSON.pretty_generate(options) %>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<%
2+
require "json"
3+
4+
# Ensure Azure CLI connection_config has a default timeout if none is set
5+
def cli_cfg_with_default_timeout(connection_cfg, blobstore_type, default_seconds: 41)
6+
cfg = (connection_cfg || {}).dup
7+
if blobstore_type == 'storage_cli'
8+
if !cfg.key?('put_timeout_in_seconds') || cfg['put_timeout_in_seconds'].to_s.empty?
9+
cfg['put_timeout_in_seconds'] = default_seconds.to_s
10+
end
11+
end
12+
cfg
13+
end
14+
15+
# helper: add key only when value is present
16+
def add_optional(h, key, val)
17+
return if val.nil?
18+
return if val.respond_to?(:empty?) && val.empty?
19+
h[key] = val
20+
end
21+
22+
l = link("cloud_controller_internal")
23+
24+
scope = "cc.droplets.connection_config"
25+
provider = l.p("cc.droplets.blobstore_provider", nil)
26+
options = {}
27+
28+
if provider == "AzureRM"
29+
options["provider"] = provider
30+
options["account_name"] = l.p("#{scope}.azure_storage_account_name")
31+
options["container_name"] = l.p("#{scope}.container_name")
32+
options["account_key"] = l.p("#{scope}.azure_storage_access_key")
33+
add_optional(options, "environment", l.p("#{scope}.environment", "AzureCloud"))
34+
add_optional(options, "put_timeout_in_seconds", l.p("#{scope}.put_timeout_in_seconds", nil))
35+
options = cli_cfg_with_default_timeout(options, 'storage_cli')
36+
end
37+
38+
if provider == "Google"
39+
options["provider"] = provider
40+
options["credentials_source"] = "static"
41+
options["json_key"] = l.p("#{scope}.google_json_key_string")
42+
options["bucket_name"] = l.p("#{scope}.bucket_name")
43+
add_optional(options, "storage_class", l.p("#{scope}.storage_class", nil))
44+
add_optional(options, "encryption_key", l.p("#{scope}.encryption_key", nil))
45+
end
46+
47+
if provider == "AWS"
48+
options["provider"] = provider
49+
options["bucket_name"] = l.p("#{scope}.bucket_name")
50+
options["credentials_source"] = "static"
51+
options["access_key_id"] = l.p("#{scope}.aws_access_key_id")
52+
options["secret_access_key"] = l.p("#{scope}.aws_secret_access_key")
53+
add_optional(options, "region", l.p("#{scope}.region", nil))
54+
add_optional(options, "host", l.p("#{scope}.host", nil))
55+
add_optional(options, "port", l.p("#{scope}.port", nil))
56+
add_optional(options, "ssl_verify_peer", l.p("#{scope}.ssl_verify_peer", nil))
57+
add_optional(options, "use_ssl", l.p("#{scope}.use_ssl", nil))
58+
add_optional(options, "signature_version", l.p("#{scope}.signature_version", nil))
59+
add_optional(options, "server_side_encryption", l.p("#{scope}.encryption", nil))
60+
add_optional(options, "sse_kms_key_id", l.p("#{scope}.x-amz-server-side-encryption-aws-kms-key-id", nil))
61+
add_optional(options, "multipart_upload", l.p("#{scope}.multipart_upload", nil))
62+
end
63+
64+
if provider == "aliyun"
65+
options["provider"] = provider
66+
options["access_key_id"] = l.p("#{scope}.aliyun_accesskey_id")
67+
options["access_key_secret"] = l.p("#{scope}.aliyun_accesskey_secret")
68+
options["endpoint"] = l.p("#{scope}.aliyun_oss_endpoint")
69+
options["bucket_name"] = l.p("#{scope}.aliyun_oss_bucket")
70+
end
71+
72+
if provider == "webdav"
73+
options["provider"] = provider
74+
options["user"] = l.p("#{scope}.username")
75+
options["password"] = l.p("#{scope}.password")
76+
options["endpoint"] = l.p("#{scope}.public_endpoint")
77+
add_optional(options, "secret", l.p("#{scope}.secret", nil))
78+
add_optional(options, "retry_attempts", l.p("#{scope}.retry_attempts", nil))
79+
80+
# TLS nested object with a Cert inside
81+
ca_cert=l.p("#{scope}.ca_cert",nil)
82+
unless ca_cert.empty?
83+
options["tls"]={"cert"=>ca_cert}
84+
end
85+
end
86+
87+
-%>
88+
<%= JSON.pretty_generate(options) %>

0 commit comments

Comments
 (0)