Skip to content

Commit 0566367

Browse files
MikaAKalvarocalleroalvaro
authored
Fix metrics (#28)
* Add a module for exporting cache metrics (#27) * add a module for exporting cache metrics * add metrics to measure cache call, delete, and put duration * remove the counter metric for cache hit * remove unnecessary telemetry execute action * remove unused metadata field --------- Co-authored-by: alvaro <[email protected]> * chore: add more error metrics and fix metric defs * chore: add credo config --------- Co-authored-by: Alvaro Callero <[email protected]> Co-authored-by: alvaro <[email protected]>
1 parent ed18dd0 commit 0566367

File tree

5 files changed

+347
-32
lines changed

5 files changed

+347
-32
lines changed

.credo.exs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
allowed_imports = [
2+
[:Absinthe],
3+
[:ChannelCase],
4+
[:DataCase],
5+
[:EctoEnum],
6+
[:Ecto],
7+
[:ExUnit, :CaptureLog],
8+
[:ExUnit],
9+
[:Mix],
10+
[:Plug],
11+
[:Router, :Helpers],
12+
[:Telemetry, :Metrics]
13+
]
14+
15+
%{
16+
configs: [
17+
%{
18+
name: "default",
19+
files: %{
20+
included: [
21+
"lib/",
22+
"src/",
23+
"test/",
24+
"web/",
25+
"apps/*/lib/",
26+
"apps/*/src/",
27+
"apps/*/test/",
28+
"apps/*/web/"
29+
],
30+
excluded: [~r"_build/", ~r"deps/"]
31+
},
32+
plugins: [],
33+
strict: true,
34+
parse_timeout: 10000,
35+
color: true,
36+
checks: [
37+
38+
# BlitzCredoChecks
39+
40+
{BlitzCredoChecks.SetWarningsAsErrorsInTest, false},
41+
{BlitzCredoChecks.DocsBeforeSpecs, []},
42+
{BlitzCredoChecks.DoctestIndent, []},
43+
{BlitzCredoChecks.NoAsyncFalse, []},
44+
{BlitzCredoChecks.NoDSLParentheses, []},
45+
{BlitzCredoChecks.NoIsBitstring, []},
46+
{BlitzCredoChecks.StrictComparison, []},
47+
{BlitzCredoChecks.UseStream, []},
48+
{BlitzCredoChecks.LowercaseTestNames, []},
49+
{BlitzCredoChecks.ImproperImport, allowed_modules: allowed_imports},
50+
51+
# Consistency Checks
52+
{Credo.Check.Consistency.ExceptionNames, []},
53+
{Credo.Check.Consistency.LineEndings, []},
54+
{Credo.Check.Consistency.ParameterPatternMatching, []},
55+
{Credo.Check.Consistency.SpaceAroundOperators, []},
56+
{Credo.Check.Consistency.SpaceInParentheses, []},
57+
{Credo.Check.Consistency.TabsOrSpaces, []},
58+
59+
# Design Checks
60+
{Credo.Check.Design.AliasUsage, false},
61+
62+
# No outstanding TODOs
63+
{Credo.Check.Design.TagTODO, []},
64+
{Credo.Check.Design.TagFIXME, []},
65+
66+
# # Readability Checks
67+
{Credo.Check.Readability.AliasOrder, false},
68+
{Credo.Check.Readability.FunctionNames, []},
69+
{Credo.Check.Readability.LargeNumbers, []},
70+
{Credo.Check.Readability.MaxLineLength, [max_length: 120]},
71+
{Credo.Check.Readability.ModuleAttributeNames, []},
72+
{Credo.Check.Readability.ModuleDoc, false},
73+
{Credo.Check.Readability.ModuleNames, []},
74+
{Credo.Check.Readability.ParenthesesInCondition, []},
75+
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
76+
{Credo.Check.Readability.PredicateFunctionNames, []},
77+
{Credo.Check.Readability.PreferImplicitTry, []},
78+
{Credo.Check.Readability.RedundantBlankLines, false},
79+
{Credo.Check.Readability.Semicolons, []},
80+
{Credo.Check.Readability.SpaceAfterCommas, false},
81+
{Credo.Check.Readability.StringSigils, []},
82+
{Credo.Check.Readability.TrailingBlankLine, false},
83+
{Credo.Check.Readability.TrailingWhiteSpace, false},
84+
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
85+
{Credo.Check.Readability.VariableNames, []},
86+
#
87+
# Refactoring Opportunities
88+
{Credo.Check.Refactor.CondStatements, []},
89+
{Credo.Check.Refactor.CyclomaticComplexity, false},
90+
{Credo.Check.Refactor.FunctionArity, []},
91+
{Credo.Check.Refactor.LongQuoteBlocks, false},
92+
{Credo.Check.Refactor.MapInto, false},
93+
{Credo.Check.Refactor.MatchInCondition, []},
94+
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
95+
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
96+
{Credo.Check.Refactor.Nesting, false},
97+
{Credo.Check.Refactor.UnlessWithElse, []},
98+
{Credo.Check.Refactor.WithClauses, []},
99+
100+
# Warnings
101+
{Credo.Check.Warning.BoolOperationOnSameValues, []},
102+
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
103+
{Credo.Check.Warning.IExPry, []},
104+
{Credo.Check.Warning.IoInspect, []},
105+
{Credo.Check.Warning.LazyLogging, false},
106+
{Credo.Check.Warning.MixEnv, false},
107+
{Credo.Check.Warning.OperationOnSameValues, []},
108+
{Credo.Check.Warning.OperationWithConstantResult, []},
109+
{Credo.Check.Warning.RaiseInsideRescue, []},
110+
{Credo.Check.Warning.UnusedEnumOperation, []},
111+
{Credo.Check.Warning.UnusedFileOperation, []},
112+
{Credo.Check.Warning.UnusedKeywordOperation, []},
113+
{Credo.Check.Warning.UnusedListOperation, []},
114+
{Credo.Check.Warning.UnusedPathOperation, []},
115+
{Credo.Check.Warning.UnusedRegexOperation, []},
116+
{Credo.Check.Warning.UnusedStringOperation, []},
117+
{Credo.Check.Warning.UnusedTupleOperation, []},
118+
{Credo.Check.Warning.UnsafeExec, []},
119+
120+
# Controversial and experimental checks
121+
{Credo.Check.Readability.StrictModuleLayout, false},
122+
{Credo.Check.Consistency.MultiAliasImportRequireUse, false},
123+
{Credo.Check.Consistency.UnusedVariableNames, false},
124+
{Credo.Check.Design.DuplicatedCode, false},
125+
{Credo.Check.Readability.AliasAs, false},
126+
{Credo.Check.Readability.MultiAlias, false},
127+
{Credo.Check.Readability.Specs, false},
128+
{Credo.Check.Readability.SinglePipe, []},
129+
{Credo.Check.Readability.WithCustomTaggedTuple, []},
130+
{Credo.Check.Refactor.ABCSize, false},
131+
{Credo.Check.Refactor.AppendSingleItem, false},
132+
{Credo.Check.Refactor.DoubleBooleanNegation, false},
133+
{Credo.Check.Refactor.ModuleDependencies, false},
134+
{Credo.Check.Refactor.NegatedIsNil, false},
135+
{Credo.Check.Refactor.PipeChainStart, []},
136+
{Credo.Check.Refactor.VariableRebinding, false},
137+
{Credo.Check.Warning.LeakyEnvironment, false},
138+
{Credo.Check.Warning.MapGetUnsafePass, false},
139+
{Credo.Check.Warning.UnsafeToAtom, false}
140+
]
141+
}
142+
]
143+
}

lib/cache.ex

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ defmodule Cache do
1010

1111
@callback opts_definition() :: Keyword.t()
1212

13-
@callback start_link(cache_opts :: Keyword.t()) :: {:ok, pid()} | {:error, {:already_started, pid()} | {:shutdown, term()} | term()} | :ignore
13+
@callback start_link(
14+
cache_opts :: Keyword.t()
15+
) :: {:ok, pid()} | {:error, {:already_started, pid()} | {:shutdown, term()} | term()} | :ignore
1416

1517
@callback put(cache_name :: atom, key :: atom | String.t(), ttl :: pos_integer, value :: any) ::
1618
:ok | ErrorMessage.t()
@@ -112,22 +114,75 @@ defmodule Cache do
112114
value = Cache.TermEncoder.encode(value, @compression_level)
113115
key = maybe_sandbox_key(key)
114116

115-
@cache_adapter.put(@cache_name, key, ttl, value, adapter_options())
117+
:telemetry.span(
118+
[:elixir_cache, :cache, :put],
119+
%{cache_name: @cache_name},
120+
fn ->
121+
result = with {:error, error} = e <- @cache_adapter.put(@cache_name, key, ttl, value, adapter_options()) do
122+
:telemetry.execute([:elixir_cache, :cache, :put, :error], %{count: 1}, %{
123+
cache_name: @cache_name,
124+
error: error
125+
})
126+
127+
e
128+
end
129+
130+
{result, %{cache_name: @cache_name}}
131+
end
132+
)
116133
end
117134

118135
def get(key) do
119136
key = maybe_sandbox_key(key)
120137

121-
with {:ok, value} when not is_nil(value) <-
122-
@cache_adapter.get(@cache_name, key, adapter_options()) do
123-
{:ok, Cache.TermEncoder.decode(value)}
124-
end
138+
:telemetry.span(
139+
[:elixir_cache, :cache, :get],
140+
%{cache_name: @cache_name},
141+
fn ->
142+
result =
143+
case @cache_adapter.get(@cache_name, key, adapter_options()) do
144+
{:ok, nil} = res ->
145+
:telemetry.execute([:elixir_cache, :cache, :get, :miss], %{count: 1}, %{
146+
cache_name: @cache_name
147+
})
148+
149+
res
150+
151+
{:ok, value} -> {:ok, Cache.TermEncoder.decode(value)}
152+
153+
{:error, error} = e ->
154+
:telemetry.execute([:elixir_cache, :cache, :get, :error], %{count: 1}, %{
155+
cache_name: @cache_name,
156+
error: error
157+
})
158+
159+
e
160+
end
161+
162+
{result, %{cache_name: @cache_name}}
163+
end
164+
)
125165
end
126166

127167
def delete(key) do
128168
key = maybe_sandbox_key(key)
129169

130-
@cache_adapter.delete(@cache_name, key, adapter_options())
170+
:telemetry.span(
171+
[:elixir_cache, :cache, :delete],
172+
%{cache_name: @cache_name},
173+
fn ->
174+
result = with {:error, error} = e <- @cache_adapter.delete(@cache_name, key, adapter_options()) do
175+
:telemetry.execute([:elixir_cache, :cache, :delete, :error], %{count: 1}, %{
176+
cache_name: @cache_name,
177+
error: error
178+
})
179+
180+
e
181+
end
182+
183+
{result, %{cache_name: @cache_name}}
184+
end
185+
)
131186
end
132187

133188
def get_or_create(key, fnc) do

lib/cache/metrics.ex

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
if Code.ensure_loaded?(PrometheusTelemetry) do
2+
defmodule Cache.Metrics do
3+
@moduledoc """
4+
Add the following metrics for elixir_cache:
5+
6+
Metrics included:
7+
- `elixir_cache.cache.get.count`
8+
- `elixir_cache.cache.get.miss.count`
9+
- `elixir_cache.cache.get.error.count`
10+
- `elixir_cache.cache.put.count`
11+
- `elixir_cache.cache.delete.count`
12+
- `elixir_cache.cache.get.duration.millisecond`
13+
"""
14+
15+
import Telemetry.Metrics, only: [counter: 2, distribution: 2]
16+
17+
@duration_unit {:native, :millisecond}
18+
@buckets PrometheusTelemetry.Config.default_millisecond_buckets()
19+
20+
def metrics do
21+
[
22+
counter("elixir_cache.cache.get.count",
23+
event_name: [:elixir_cache, :cache, :get, :start],
24+
measurement: :count,
25+
description: "Total cache calls",
26+
tags: [:cache_name]
27+
),
28+
29+
counter("elixir_cache.cache.get.miss.count",
30+
event_name: [:elixir_cache, :cache, :get, :miss],
31+
measurement: :count,
32+
description: "Cache miss count",
33+
tags: [:cache_name]
34+
),
35+
36+
counter("elixir_cache.cache.get.error.count",
37+
event_name: [:elixir_cache, :cache, :get, :error],
38+
measurement: :count,
39+
description: "Cache error count",
40+
tags: [:cache_name, :error],
41+
tag_values: &extract_error_metadata/1
42+
),
43+
44+
counter("elixir_cache.cache.put.count",
45+
event_name: [:elixir_cache, :cache, :put],
46+
measurement: :count,
47+
description: "Cache put count",
48+
tags: [:cache_name]
49+
),
50+
51+
counter("elixir_cache.cache.put.error.count",
52+
event_name: [:elixir_cache, :cache, :put, :error],
53+
measurement: :count,
54+
description: "Cache error count",
55+
tags: [:cache_name, :error],
56+
tag_values: &extract_error_metadata/1
57+
),
58+
59+
counter("elixir_cache.cache.delete.count",
60+
event_name: [:elixir_cache, :cache, :delete],
61+
measurement: :count,
62+
description: "Cache delete count",
63+
tags: [:cache_name]
64+
),
65+
66+
counter("elixir_cache.cache.delete.error.count",
67+
event_name: [:elixir_cache, :cache, :delete, :error],
68+
measurement: :count,
69+
description: "Cache error count",
70+
tags: [:cache_name, :error],
71+
tag_values: &extract_error_metadata/1
72+
),
73+
74+
distribution("elixir_cache.cache.get.duration.millisecond",
75+
event_name: [:elixir_cache, :cache, :get, :stop],
76+
measurement: :duration,
77+
description: "Time taken for cache get",
78+
tags: [:cache_name],
79+
unit: @duration_unit,
80+
reporter_options: [buckets: @buckets]
81+
),
82+
83+
distribution("elixir_cache.cache.put.duration.millisecond",
84+
event_name: [:elixir_cache, :cache, :put, :stop],
85+
measurement: :duration,
86+
description: "Time taken for cache put",
87+
tags: [:cache_name],
88+
unit: @duration_unit,
89+
reporter_options: [buckets: @buckets]
90+
),
91+
92+
distribution("elixir_cache.cache.delete.duration.millisecond",
93+
event_name: [:elixir_cache, :cache, :delete, :stop],
94+
measurement: :duration,
95+
description: "Time taken for cache delete",
96+
tags: [:cache_name],
97+
unit: @duration_unit,
98+
reporter_options: [buckets: @buckets]
99+
)
100+
]
101+
end
102+
103+
defp extract_error_metadata(metadata) do
104+
metadata
105+
|> Map.take([:error, :cache_name])
106+
|> Map.update(:error, "unknown", fn
107+
%ErrorMessage{code: code, error: error} -> "code}: #{error}"
108+
reason when is_atom(reason) -> to_string(reason)
109+
reason when is_binary(reason) -> reason
110+
_ -> "unknown"
111+
end)
112+
end
113+
end
114+
end

0 commit comments

Comments
 (0)