-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmemory.sql
365 lines (342 loc) · 10.5 KB
/
memory.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET LOCK_TIMEOUT 10000;
DECLARE @ServiceName nvarchar(100)
SET @ServiceName =
CASE
WHEN @@SERVICENAME = 'MSSQLSERVER' THEN 'SQLServer:'
ELSE 'MSSQL$' + @@SERVICENAME + ':'
END
DECLARE @Perf TABLE (
object_name nvarchar(20),
counter_name nvarchar(128),
instance_name nvarchar(128),
cntr_value bigint,
formatted_value numeric(20, 2),
shortname nvarchar(20)
)
INSERT INTO @Perf (object_name, counter_name, instance_name, cntr_value, formatted_value, shortname)
SELECT
CASE
WHEN CHARINDEX('Memory Manager', object_name) > 0 THEN 'Memory Manager'
WHEN CHARINDEX('Buffer Manager', object_name) > 0 THEN 'Buffer Manager'
WHEN CHARINDEX('Plan Cache', object_name) > 0 THEN 'Plan Cache'
WHEN CHARINDEX('Buffer Node', object_name) > 0 THEN 'Buffer Node' -- 2008
WHEN CHARINDEX('Memory Node', object_name) > 0 THEN 'Memory Node' -- 2012
WHEN CHARINDEX('Cursor', object_name) > 0 THEN 'Cursor'
ELSE NULL
END AS object_name,
CAST(RTRIM(counter_name) AS nvarchar(100)) AS counter_name,
RTRIM(instance_name) AS instance_name,
cntr_value,
CAST(NULL AS decimal(20, 2)) AS formatted_value,
SUBSTRING(counter_name, 1, PATINDEX('% %', counter_name)) shortname
FROM sys.dm_os_performance_counters
WHERE (object_name LIKE @ServiceName + 'Buffer Node%' -- LIKE is faster than =. I have no idea why
OR object_name LIKE @ServiceName + 'Buffer Manager%'
OR object_name LIKE @ServiceName + 'Memory Node%'
OR object_name LIKE @ServiceName + 'Plan Cache%')
AND (counter_name LIKE '%pages %'
OR counter_name LIKE '%Node Memory (KB)%'
OR counter_name = 'Page life expectancy'
)
OR (object_name = @ServiceName + 'Memory Manager'
AND counter_name IN ('Granted Workspace Memory (KB)', 'Maximum Workspace Memory (KB)',
'Memory Grants Outstanding', 'Memory Grants Pending',
'Target Server Memory (KB)', 'Total Server Memory (KB)',
'Connection Memory (KB)', 'Lock Memory (KB)',
'Optimizer Memory (KB)', 'SQL Cache Memory (KB)',
-- for 2012
'Free Memory (KB)', 'Reserved Server Memory (KB)',
'Database Cache Memory (KB)', 'Stolen Server Memory (KB)')
)
OR (object_name LIKE @ServiceName + 'Cursor Manager by Type%'
AND counter_name = 'Cursor memory usage'
AND instance_name = '_Total'
)
-- Add unit to 'Cursor memory usage'
UPDATE @Perf
SET counter_name = counter_name + ' (KB)'
WHERE counter_name = 'Cursor memory usage'
-- Convert values from pages and KB to MB and rename counters accordingly
UPDATE @Perf
SET counter_name = REPLACE(REPLACE(REPLACE(counter_name, ' pages', ''), ' (KB)', ''), ' (MB)', ''),
formatted_value =
CASE
WHEN counter_name LIKE '%pages' THEN cntr_value / 128.
WHEN counter_name LIKE '%(KB)' THEN cntr_value / 1024.
ELSE cntr_value
END
-- Delete some pre 2012 counters for 2012 in order to remove duplicates
DELETE P2008
FROM @Perf P2008
INNER JOIN @Perf P2012
ON REPLACE(P2008.object_name, 'Buffer', 'Memory') = P2012.object_name
AND P2008.shortname = P2012.shortname
WHERE P2008.object_name IN ('Buffer Manager', 'Buffer Node')
-- Update counter/object names so they look like in 2012
UPDATE PC
SET object_name = REPLACE(object_name, 'Buffer', 'Memory'),
counter_name = ISNULL(M.NewName, counter_name)
FROM @Perf PC
LEFT JOIN (SELECT
'Free' AS OldName,
'Free Memory' AS NewName
UNION ALL
SELECT
'Database',
'Database Cache Memory'
UNION ALL
SELECT
'Stolen',
'Stolen Server Memory'
UNION ALL
SELECT
'Reserved',
'Reserved Server Memory'
UNION ALL
SELECT
'Foreign',
'Foreign Node Memory') M
ON M.OldName = PC.counter_name
AND NewName NOT IN (SELECT
counter_name
FROM @Perf
WHERE object_name = 'Memory Manager')
WHERE object_name IN ('Buffer Manager', 'Buffer Node')
-- Build Memory Tree
DECLARE @MemTree TABLE (
Id int,
ParentId int,
counter_name nvarchar(128),
formatted_value numeric(20, 2),
shortname nvarchar(20)
)
-- Level 5
INSERT @MemTree (Id, ParentId, counter_name, formatted_value, shortname)
SELECT
Id = 1226,
ParentId = 1225,
instance_name AS counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Plan Cache'
AND counter_name IN ('Cache')
AND instance_name <> '_Total'
-- Level 4
INSERT @MemTree (Id, ParentId, counter_name, formatted_value, shortname)
SELECT
Id = 1225,
ParentId = 1220,
'Plan ' + counter_name AS counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Plan Cache'
AND counter_name IN ('Cache')
AND instance_name = '_Total'
UNION ALL
SELECT
Id = 1222,
ParentId = 1220,
counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Cursor'
OR (object_name = 'Memory Manager'
AND shortname IN ('Connection', 'Lock', 'Optimizer', 'SQL'))
UNION ALL
SELECT
Id = 1112,
ParentId = 1110,
counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Memory Manager'
AND shortname IN ('Reserved')
UNION ALL
SELECT
Id = P.ParentID + 1,
ParentID = P.ParentID,
'Used Workspace Memory' AS counter_name,
SUM(used_memory_kb) / 1024. AS formatted_value,
NULL AS shortname
FROM sys.dm_exec_query_resource_semaphores
CROSS JOIN (SELECT
1220 AS ParentID
UNION ALL
SELECT
1110) P
GROUP BY P.ParentID
-- Level 3
INSERT @MemTree (Id, ParentId, counter_name, formatted_value, shortname)
SELECT
Id =
CASE counter_name
WHEN 'Granted Workspace Memory' THEN 1110
WHEN 'Stolen Server Memory' THEN 1220
ELSE 1210
END,
ParentId =
CASE counter_name
WHEN 'Granted Workspace Memory' THEN 1100
ELSE 1200
END,
counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Memory Manager'
AND counter_name IN ('Stolen Server Memory', 'Database Cache Memory', 'Free Memory', 'Granted Workspace Memory')
-- Level 2
INSERT @MemTree (Id, ParentId, counter_name, formatted_value, shortname)
SELECT
Id =
CASE
WHEN counter_name = 'Maximum Workspace Memory' THEN 1100
ELSE 1200
END,
ParentId = 1000,
counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Memory Manager'
AND counter_name IN ('Total Server Memory', 'Maximum Workspace Memory')
-- Level 1
INSERT @MemTree (Id, ParentId, counter_name, formatted_value, shortname)
SELECT
Id = 1000,
ParentId = NULL,
counter_name,
formatted_value,
shortname
FROM @Perf
WHERE object_name = 'Memory Manager'
AND counter_name IN ('Target Server Memory')
-- Level 4 -- 'Other Stolen Server Memory' = 'Stolen Server Memory' - SUM(Children of 'Stolen Server Memory')
INSERT @MemTree (Id, ParentId, counter_name, formatted_value, shortname)
SELECT
Id = 1222,
ParentId = 1220,
counter_name = '<Other Memory Clerks>',
formatted_value = (SELECT
SSM.formatted_value
FROM @MemTree SSM
WHERE Id = 1220)
- SUM(formatted_value),
shortname = 'Other Stolen'
FROM @MemTree
WHERE ParentId = 1220
-- Results:
-- PLE and Memory Grants
SELECT
[Counter Name] = P.counter_name + ISNULL(' (Node: ' + NULLIF(P.instance_name, '') + ')', ''),
cntr_value AS Value,
RecommendedMinimum =
CASE
WHEN P.counter_name = 'Page life expectancy' AND
R.Value <= 300 -- no less than 300
THEN 300
WHEN P.counter_name = 'Page life expectancy' AND
R.Value > 300 THEN R.Value
ELSE NULL
END
FROM @Perf P
LEFT JOIN -- Recommended PLE calculations
(SELECT
object_name,
counter_name,
instance_name,
CEILING(formatted_value / 4096. * 5) * 60 AS Value -- 300 per every 4GB of Buffer Pool memory or around 60 seconds (1 minute) per every 819MB
FROM @Perf PD
WHERE counter_name = 'Database Cache Memory') R
ON R.object_name = P.object_name
AND R.instance_name = P.instance_name
WHERE (P.object_name = 'Memory Manager'
AND P.counter_name IN ('Memory Grants Outstanding', 'Memory Grants Pending', 'Page life expectancy')
)
OR -- For NUMA
(
P.object_name = 'Memory Node'
AND P.counter_name = 'Page life expectancy'
AND (SELECT
COUNT(DISTINCT instance_name)
FROM @Perf
WHERE object_name = 'Memory Node')
> 1
)
ORDER BY P.counter_name DESC, P.instance_name
-- Get physical memory
-- You can also extract this information from sys.dm_os_sys_info but the column names have changed starting from 2012
IF OBJECT_ID('tempdb..#msver') IS NOT NULL
DROP TABLE #msver
CREATE TABLE #msver (
ID int,
Name sysname,
Internal_Value int,
Value nvarchar(512)
)
INSERT #msver EXEC master.dbo.xp_msver 'PhysicalMemory'
-- Physical memory, config parameters and Target memory
SELECT
min_server_mb = (SELECT
CAST(value_in_use AS decimal(20, 2))
FROM sys.configurations
WHERE name = 'min server memory (MB)'),
max_server_mb = (SELECT
CAST(value_in_use AS decimal(20, 2))
FROM sys.configurations
WHERE name = 'max server memory (MB)'),
target_mb = (SELECT
formatted_value
FROM @Perf
WHERE object_name = 'Memory Manager'
AND counter_name IN ('Target Server Memory')),
physical_mb = CAST(Internal_Value AS decimal(20, 2))
FROM #msver
-- Memory tree
;
WITH CTE
AS (SELECT
0 AS lvl,
counter_name,
formatted_value,
Id,
NULL AS ParentId,
shortname,
formatted_value AS TargetServerMemory,
CAST(NULL AS decimal(20, 4)) AS Perc,
CAST(NULL AS decimal(20, 4)) AS PercOfTarget
FROM @MemTree
WHERE ParentId IS NULL
UNION ALL
SELECT
CTE.lvl + 1,
CAST(REPLICATE(' ', 6 * (CTE.lvl)) + NCHAR(124) + REPLICATE(NCHAR(183), 3) + MT.counter_name AS nvarchar(128)),
MT.formatted_value,
MT.Id,
MT.ParentId,
MT.shortname,
CTE.TargetServerMemory,
CAST(ISNULL(1.0 * MT.formatted_value / NULLIF(CTE.formatted_value, 0), 0) AS decimal(20, 4)) AS Perc,
CAST(ISNULL(1.0 * MT.formatted_value / NULLIF(CTE.TargetServerMemory, 0), 0) AS decimal(20, 4)) AS PercOfTarget
FROM @MemTree MT
INNER JOIN CTE
ON MT.ParentId = CTE.Id)
SELECT
counter_name AS [Counter Name],
CASE
WHEN formatted_value > 0 THEN formatted_value
ELSE NULL
END AS [Memory MB],
Perc AS [% of Parent],
CASE
WHEN lvl >= 2 THEN PercOfTarget
ELSE NULL
END AS [% of Target]
FROM CTE
ORDER BY ISNULL(Id, 10000), formatted_value DESC;